Skip to content

Commit 5330664

Browse files
RoyLinRoyLin
authored andcommitted
fix(update): bridge legacy SRT archive checks
1 parent 0b4bacc commit 5330664

9 files changed

Lines changed: 164 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ jobs:
551551
bin: a3s
552552
target: ${{ matrix.target }}
553553
archive: a3s-$tag-$target
554-
include: web,${{ matrix.helper }}
554+
include: web,release-compat,${{ matrix.helper }}
555555
tar: all
556556
zip: windows
557557
checksum: sha256
@@ -597,6 +597,27 @@ jobs:
597597
exit 1
598598
fi
599599
done
600+
bridge_root='release-compat/support/managed-srt'
601+
bridge_lock="${bridge_root}/package-lock.json"
602+
bridge_cli="${bridge_root}/node_modules/@anthropic-ai/sandbox-runtime/dist/cli.js"
603+
tar_entries="$(tar -tzf "${base}.tar.gz")"
604+
for bridge_path in "$bridge_lock" "$bridge_cli"; do
605+
bridge_count="$(grep -Ec "(^|/)${bridge_path}$" <<<"$tar_entries" || true)"
606+
if [[ "$bridge_count" -ne 1 ]]; then
607+
echo "::error::Release archive must contain exactly one legacy self-update bridge path ${bridge_path}; found ${bridge_count}"
608+
exit 1
609+
fi
610+
done
611+
if [[ "$RELEASE_TARGET" == "x86_64-pc-windows-msvc" ]]; then
612+
zip_entries="$(unzip -Z1 "${base}.zip")"
613+
for bridge_path in "$bridge_lock" "$bridge_cli"; do
614+
bridge_count="$(grep -Ec "(^|/)${bridge_path}$" <<<"$zip_entries" || true)"
615+
if [[ "$bridge_count" -ne 1 ]]; then
616+
echo "::error::Windows release archive must contain exactly one legacy self-update bridge path ${bridge_path}; found ${bridge_count}"
617+
exit 1
618+
fi
619+
done
620+
fi
600621
gh release upload "$tag" "${assets[@]}" --clobber
601622
602623
crate:

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Restored one-command self-updates from A3S 0.9.9 through 0.10.10 by adding a
13+
minimal, fail-closed retirement marker to release archives for their legacy
14+
managed-SRT payload check. The Anthropic SRT runtime remains removed and the
15+
marker is never discovered or executed by current A3S versions.
16+
1017
## [0.10.14] - 2026-07-29
1118

1219
### Fixed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ repository = "https://github.com/A3S-Lab/Cli"
88
homepage = "https://github.com/A3S-Lab/a3s"
99
keywords = ["a3s", "cli", "agent", "tui", "coding"]
1010
categories = ["command-line-utilities"]
11+
exclude = ["release-compat/**", "tests/legacy_self_update_bridge.rs"]
1112

1213
[[bin]]
1314
name = "a3s"

release-compat/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Legacy self-update bridge
2+
3+
This directory is included only in release archives.
4+
5+
A3S 0.9.9 through 0.10.10 required every self-update archive to contain a
6+
`support/managed-srt` tree before they would install the new binary. A3S 0.10.11
7+
removed the Anthropic SRT runtime, so omitting that tree made those older
8+
clients unable to update themselves.
9+
10+
The nested package is an inert, fail-closed compatibility marker. Current A3S
11+
versions do not discover or execute it, and it must never acquire sandbox
12+
runtime dependencies or implementation code.

release-compat/support/managed-srt/node_modules/@anthropic-ai/sandbox-runtime/dist/cli.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

release-compat/support/managed-srt/node_modules/@anthropic-ai/sandbox-runtime/package.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

release-compat/support/managed-srt/package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "a3s-managed-srt-retirement-bridge",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "Inert release marker for legacy A3S self-updaters",
6+
"dependencies": {
7+
"@anthropic-ai/sandbox-runtime": "0.0.0-a3s-retired"
8+
}
9+
}

tests/legacy_self_update_bridge.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::fs;
2+
use std::path::{Path, PathBuf};
3+
4+
use serde_json::Value;
5+
6+
const RETIRED_PACKAGE_VERSION: &str = "0.0.0-a3s-retired";
7+
8+
fn repository_path(relative: impl AsRef<Path>) -> PathBuf {
9+
Path::new(env!("CARGO_MANIFEST_DIR")).join(relative)
10+
}
11+
12+
#[test]
13+
fn bridge_matches_the_legacy_updater_contract_and_fails_closed() {
14+
let root = repository_path("release-compat/support/managed-srt");
15+
let package_path = root.join("node_modules/@anthropic-ai/sandbox-runtime/package.json");
16+
let package: Value = serde_json::from_slice(&fs::read(&package_path).unwrap()).unwrap();
17+
assert_eq!(
18+
package.get("name").and_then(Value::as_str),
19+
Some("@anthropic-ai/sandbox-runtime")
20+
);
21+
assert_eq!(
22+
package.get("version").and_then(Value::as_str),
23+
Some(RETIRED_PACKAGE_VERSION)
24+
);
25+
26+
let lock_path = root.join("package-lock.json");
27+
let lock: Value = serde_json::from_slice(&fs::read(&lock_path).unwrap()).unwrap();
28+
assert_eq!(lock.get("lockfileVersion").and_then(Value::as_u64), Some(3));
29+
assert_eq!(
30+
lock.pointer("/packages/node_modules~1@anthropic-ai~1sandbox-runtime/version")
31+
.and_then(Value::as_str),
32+
Some(RETIRED_PACKAGE_VERSION)
33+
);
34+
assert_eq!(
35+
lock.pointer("/packages//dependencies/@anthropic-ai~1sandbox-runtime")
36+
.and_then(Value::as_str),
37+
Some(RETIRED_PACKAGE_VERSION)
38+
);
39+
40+
let cli_path = root.join("node_modules/@anthropic-ai/sandbox-runtime/dist/cli.js");
41+
let cli = fs::read_to_string(&cli_path).unwrap();
42+
assert!(cli.contains("Anthropic SRT was removed from A3S"));
43+
assert!(cli.contains("process.exitCode = 1"));
44+
for forbidden in ["child_process", "require(", "import ", "eval("] {
45+
assert!(
46+
!cli.contains(forbidden),
47+
"bridge must not contain {forbidden}"
48+
);
49+
}
50+
51+
#[cfg(unix)]
52+
{
53+
use std::os::unix::fs::PermissionsExt;
54+
assert_ne!(
55+
fs::metadata(cli_path).unwrap().permissions().mode() & 0o111,
56+
0
57+
);
58+
}
59+
}
60+
61+
#[test]
62+
fn release_workflow_packages_and_verifies_the_bridge() {
63+
let workflow = fs::read_to_string(repository_path(".github/workflows/release.yml")).unwrap();
64+
assert!(workflow.contains("include: web,release-compat,${{ matrix.helper }}"));
65+
assert!(workflow.contains("bridge_root='release-compat/support/managed-srt'"));
66+
assert!(workflow.contains("bridge_lock=\"${bridge_root}/package-lock.json\""));
67+
assert!(workflow.contains(
68+
"bridge_cli=\"${bridge_root}/node_modules/@anthropic-ai/sandbox-runtime/dist/cli.js\""
69+
));
70+
71+
let manifest = fs::read_to_string(repository_path("Cargo.toml")).unwrap();
72+
assert!(manifest
73+
.contains("exclude = [\"release-compat/**\", \"tests/legacy_self_update_bridge.rs\"]"));
74+
}

0 commit comments

Comments
 (0)