Skip to content

Commit 46d0b75

Browse files
committed
fix: enforce supported uv runtime
1 parent 30db1fe commit 46d0b75

9 files changed

Lines changed: 95 additions & 13 deletions

File tree

RELEASE_MANIFEST.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
},
187187
"docs/GATE_EVIDENCE.md": {
188188
"path": "docs/GATE_EVIDENCE.md",
189-
"sha256": "4316709832aa201213df0947c738914c45e710223a856ac61effa6d38327e5a0"
189+
"sha256": "bd78fcdf4615bcd672174d2b08583064196630c21063b9ce725708e11beb6957"
190190
},
191191
"docs/RELEASE_MANIFEST.md": {
192192
"path": "docs/RELEASE_MANIFEST.md",

docs/GATE_EVIDENCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Gate statuses used here:
3535
| Release input, package, and clean install | `.venv/bin/python scripts/verify_release.py`; `SOURCE_DATE_EPOCH=1580601600 PYTHONDONTWRITEBYTECODE=1 UV_CACHE_DIR=/tmp/uv-cache UV_LINK_MODE=copy uv build --offline --no-build-isolation --out-dir /tmp/agentnet-final-build-20260713`; clean-wheel install/init/status/backup/restore/demos in `evidence/local/2026-07-13-final/manifest.json` | Verifier PASS; locked build backend produced retained, content-validated sdist/wheel evidence; new-environment install and clean schema-v1 status passed | Local package evidence only; it is not an independent signature, SBOM/provenance publication, root ceremony, maintained KMS, or cross-platform qualification. |
3636
| npm `0.1.3` packaging patch | `SOURCE_DATE_EPOCH=1580601600 PYTHONDONTWRITEBYTECODE=1 UV_CACHE_DIR=/tmp/uv-cache UV_LINK_MODE=copy uv build --offline --no-build-isolation --out-dir /tmp/agentnet-build-0.1.3-a`; repeated in an independent output directory | Byte-identical retained wheel and sdist; current package tests and release verifier pass | Marketing, discoverability metadata, preview image, and version-only runtime edits; prior PostgreSQL/security evidence remains historical and is not promoted. |
3737
| npm `0.1.4` installer repair | Retained build evidence plus clean public npm-tarball retest from an unrelated cwd | Python artifacts were byte-identical and bounded cwd/version checks passed, but full packed verification later reproduced `894 passed, 16 failed, 7 expected PostgreSQL skips`; activation blocked | The public package omitted six verifier inputs and allowed host-dependent Python minor selection. This row preserves the failed packed-artifact outcome; no production evidence is promoted. |
38-
| npm `0.1.5` packed-artifact correction | Exact CPython `3.13.13`; two independent reproducible Python builds; source release suite; clean installed npm tarball full `agentnet verify` from unrelated cwd | Byte-identical wheel/sdist; source `910 passed, 7 expected PostgreSQL skips`; packed `910 passed, 7 expected PostgreSQL skips`; release verifier PASS | Exact evidence-file inclusion, portable artifact-retention evidence, interpreter selection, and full packed-tarball prepublication gate only; no identity, authorization, protocol, storage, delivery, A2A, or production-gate semantics change. |
38+
| npm `0.1.5` packed-artifact correction | Exact CPython `3.13.13`; `uv>=0.11.28`; two independent reproducible Python builds; source release suite; clean installed npm tarball full `agentnet verify` from unrelated cwd | Byte-identical wheel/sdist; source `911 passed, 7 expected PostgreSQL skips`; packed `911 passed, 7 expected PostgreSQL skips`; release verifier PASS | Exact evidence-file inclusion, portable artifact-retention evidence, interpreter/toolchain selection, explicit old-uv rejection without host mutation, and full packed-tarball prepublication gate only; no identity, authorization, protocol, storage, delivery, A2A, or production-gate semantics change. |
3939
| PostgreSQL/storage corpus | `AGENTNET_TEST_POSTGRES_URL='postgresql:///agentnet_test_final?host=/tmp/agentnet-pgsocket-20260713-final&port=55432' AGENTNET_TEST_POSTGRES_ALLOW_MUTATION=1 PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src .venv/bin/pytest -q -p no:cacheprovider tests/production/test_postgres_runtime.py` | 44 passed, 0 skipped | Seven cases use a real local UTF-8 PostgreSQL 18.4 database for pre-release rejection, clean schema-v1 creation/checksum/DDL tamper fencing, mailbox visibility, reconnect fencing, quota admission, breaker CAS, and conflict/adjudication serialization; 37 are hermetic connection/SQLite contracts. No multi-node HA/failover/PITR or PostgreSQL restore-execution claim. |
4040
| Four installed deterministic harnesses | `PYTHONDONTWRITEBYTECODE=1 UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q -p no:cacheprovider tests/adapters/test_installed_live_inference.py` | 8 passed in 1.94s | Exact installed version probes and deterministic private lifecycles only; no inference or semantic conformance. |
4141
| Native A2A local regression | `.venv/bin/pytest -q tests/a2a` | Included in the broad run; all checked-in A2A tests passed | H/L SDK, mapping, security, callback, persistence, and gateway evidence only. |

docs/npm-installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pi install git:github.com/misunders2d/agentnet
2424

2525
- Linux
2626
- Node.js 22.19 or newer
27-
- [`uv`](https://docs.astral.sh/uv/) on `PATH`
27+
- [`uv`](https://docs.astral.sh/uv/) 0.11.28 or newer on `PATH`
2828

2929
The launcher uses the committed `uv.lock` and selects the release-certified CPython `3.13.13` runtime. The Python package remains compatible with `>=3.13,<3.15`, but the npm launcher is deliberately stricter so different hosts cannot silently verify the same release with different interpreter minors. It keeps an environment keyed by package version and install identity under the current user's state directory. Global npm and Pi-managed copies therefore do not rebind each other's Python environment. Override that location with an absolute `AGENTNET_NPM_RUNTIME_DIR` when needed.
3030

@@ -47,7 +47,7 @@ Also verify `uv` independently:
4747
command -v uv
4848
```
4949

50-
Install `uv` from <https://docs.astral.sh/uv/> if that command prints nothing.
50+
Run `uv --version` and require 0.11.28 or newer. Install or explicitly upgrade `uv` from <https://docs.astral.sh/uv/> when needed; AgentNet never modifies the host `uv` installation automatically.
5151

5252
## Verify a source checkout before publication
5353

Binary file not shown.
19 Bytes
Binary file not shown.
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"run_id": "2026-07-14-v0.1.5",
3-
"evaluated_at": "2026-07-14T15:31:00+00:00",
3+
"evaluated_at": "2026-07-14T16:25:00+00:00",
44
"scope": "focused_packed_npm_release_repair",
55
"release_certified": false,
66
"release_source_tree_sha256": "5163628bf1e7b14a6dad03827fd5e51edd10f01ded21293a78be43d4acba1d08",
77
"prior_runtime_evidence": "evidence/local/2026-07-13-final/manifest.json",
88
"supersedes_package_evidence": "evidence/local/2026-07-14-v0.1.4/manifest.json",
9-
"change_boundary": "Exact npm evidence-file inclusion, artifact-retention metadata, certified npm interpreter selection, and a full clean packed-tarball prepublication gate; no identity, authorization, protocol, storage, delivery, A2A, or production-gate semantics change.",
9+
"change_boundary": "Exact npm evidence-file inclusion, artifact-retention metadata, certified npm interpreter selection, explicit uv 0.11.28+ enforcement without host mutation, and a full clean packed-tarball prepublication gate; no identity, authorization, protocol, storage, delivery, A2A, or production-gate semantics change.",
1010
"commands": [
1111
{
1212
"command": "public @misunders2d/agentnet@0.1.4 tarball clean install; full agentnet verify from unrelated cwd",
@@ -18,11 +18,11 @@
1818
},
1919
{
2020
"command": "release source suite with the three separately gated host-specific files excluded",
21-
"result": "910 passed; 7 expected PostgreSQL skips"
21+
"result": "911 passed; 7 expected PostgreSQL skips"
2222
},
2323
{
2424
"command": "npm run check:packed",
25-
"result": "clean installed npm tarball; CPython 3.13.13; full agentnet verify from unrelated cwd; 910 passed; 7 expected PostgreSQL skips"
25+
"result": "clean installed npm tarball; uv 0.11.28+ enforced; CPython 3.13.13; full agentnet verify from unrelated cwd; 911 passed; 7 expected PostgreSQL skips"
2626
},
2727
{
2828
"command": "uv run --python 3.13.13 python scripts/verify_release.py",
@@ -32,11 +32,11 @@
3232
"artifacts": [
3333
{
3434
"path": "evidence/local/2026-07-14-v0.1.5/artifacts/agentnet-0.1.5.tar.gz",
35-
"sha256": "cb98099ca8acf8aaa500684888fdd1e0693bcca6590d40164c74be4180d85203"
35+
"sha256": "b77a614bba6c23162a671bd484787018d350d8d06e6bfa0e1e6358565f6551e6"
3636
},
3737
{
3838
"path": "evidence/local/2026-07-14-v0.1.5/artifacts/agentnet-0.1.5-py3-none-any.whl",
39-
"sha256": "4ce3d691c3441ae1e6af4fc0e3b1a91819818f1ef1bd1fe9069681f0f761cc40"
39+
"sha256": "1428a65792a2c4170d5f002d809bb8c43a9378607940a33a4e76fb876071f9c6"
4040
}
4141
]
4242
}

npm/bin/agentnet.mjs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
import { spawn } from "node:child_process";
3+
import { spawn, spawnSync } from "node:child_process";
44
import { createHash } from "node:crypto";
55
import { chmodSync, lstatSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
66
import os from "node:os";
@@ -21,6 +21,41 @@ if (process.platform !== "linux") {
2121
process.exit(1);
2222
}
2323

24+
const uvExecutable = process.env.AGENTNET_UV || "uv";
25+
const minimumUvVersion = [0, 11, 28];
26+
const uvVersion = spawnSync(uvExecutable, ["--version"], {
27+
encoding: "utf8",
28+
shell: false,
29+
});
30+
if (uvVersion.error?.code === "ENOENT") {
31+
console.error("AgentNet requires uv 0.11.28 or newer on PATH: https://docs.astral.sh/uv/");
32+
process.exit(1);
33+
}
34+
if (uvVersion.error || uvVersion.status !== 0) {
35+
console.error("AgentNet could not determine the installed uv version.");
36+
process.exit(1);
37+
}
38+
const uvMatch = /^uv (\d+)\.(\d+)\.(\d+)(?:\s|$)/.exec(uvVersion.stdout.trim());
39+
if (!uvMatch) {
40+
console.error("AgentNet could not parse the installed uv version.");
41+
process.exit(1);
42+
}
43+
const actualUvVersion = uvMatch.slice(1).map(Number);
44+
const versionAtLeast = (actual, minimum) => {
45+
for (let index = 0; index < minimum.length; index += 1) {
46+
if (actual[index] > minimum[index]) return true;
47+
if (actual[index] < minimum[index]) return false;
48+
}
49+
return true;
50+
};
51+
if (!versionAtLeast(actualUvVersion, minimumUvVersion)) {
52+
console.error(
53+
`AgentNet requires uv 0.11.28 or newer; found ${actualUvVersion.join(".")}. ` +
54+
"Upgrade uv explicitly, then retry.",
55+
);
56+
process.exit(1);
57+
}
58+
2459
const stateRoot = process.env.XDG_STATE_HOME
2560
? path.resolve(process.env.XDG_STATE_HOME)
2661
: path.join(os.homedir(), ".local", "state");
@@ -50,7 +85,6 @@ const uvArguments = [
5085
if (verify) uvArguments.push("--extra", "test");
5186
uvArguments.push("agentnet", ...userArguments);
5287

53-
const uvExecutable = process.env.AGENTNET_UV || "uv";
5488
const child = spawn(uvExecutable, uvArguments, {
5589
stdio: "inherit",
5690
env: {

npm/scripts/check-package.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,8 @@ const launcherText = readFileSync(launcher, "utf8");
7474
if (!launcherText.includes('"3.13.13"') || launcherText.includes('">=3.13,<3.15"')) {
7575
fail("npm launcher is not pinned to certified CPython 3.13.13");
7676
}
77+
if (!launcherText.includes("minimumUvVersion = [0, 11, 28]")) {
78+
fail("npm launcher does not enforce the minimum supported uv version");
79+
}
7780

7881
if (!process.exitCode) console.log("npm package check: PASS");

tests/conformance/test_npm_package.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,48 @@ def test_npm_launcher_is_locked_shell_free_and_user_scoped() -> None:
9999
)["scripts"]
100100

101101

102+
@pytest.mark.skipif(shutil.which("node") is None, reason="Node.js is unavailable")
103+
def test_npm_launcher_rejects_unsupported_uv_before_runtime_creation(tmp_path: Path) -> None:
104+
package_root = tmp_path / "package"
105+
launcher = package_root / "npm/bin/agentnet.mjs"
106+
launcher.parent.mkdir(parents=True)
107+
shutil.copy2(ROOT / "npm/bin/agentnet.mjs", launcher)
108+
(package_root / "package.json").write_text(
109+
json.dumps({"version": "0.1.5"}),
110+
encoding="utf-8",
111+
)
112+
fake_uv = tmp_path / "old-uv"
113+
fake_uv.write_text(
114+
f"#!{sys.executable}\nprint('uv 0.9.13')\n",
115+
encoding="utf-8",
116+
)
117+
fake_uv.chmod(0o700)
118+
state_root = tmp_path / "state"
119+
environment = os.environ.copy()
120+
environment.update(
121+
{
122+
"AGENTNET_UV": str(fake_uv),
123+
"XDG_STATE_HOME": str(state_root),
124+
}
125+
)
126+
127+
completed = subprocess.run(
128+
["node", str(launcher), "--version"],
129+
check=False,
130+
capture_output=True,
131+
text=True,
132+
env=environment,
133+
timeout=30,
134+
)
135+
136+
assert completed.returncode == 1
137+
assert completed.stderr == (
138+
"AgentNet requires uv 0.11.28 or newer; found 0.9.13. "
139+
"Upgrade uv explicitly, then retry.\n"
140+
)
141+
assert not state_root.exists()
142+
143+
102144
@pytest.mark.skipif(shutil.which("npm") is None, reason="npm is unavailable")
103145
def test_npm_dry_run_tarball_contains_release_verifier_inputs() -> None:
104146
completed = subprocess.run(
@@ -131,7 +173,10 @@ def test_same_version_npm_installs_use_distinct_runtime_roots(tmp_path: Path) ->
131173
fake_uv = tmp_path / "fake-uv"
132174
fake_uv.write_text(
133175
f"#!{sys.executable}\n"
134-
"import json, os\n"
176+
"import json, os, sys\n"
177+
"if sys.argv[1:] == ['--version']:\n"
178+
" print('uv 0.11.28')\n"
179+
" raise SystemExit(0)\n"
135180
"from pathlib import Path\n"
136181
"Path(os.environ['AGENTNET_TEST_CAPTURE']).write_text(json.dumps({\n"
137182
" 'package_root': os.environ['AGENTNET_PACKAGE_ROOT'],\n"

0 commit comments

Comments
 (0)