Refuse stale installed owner packages despite matching version metadata
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
be42de7caf
commit
7a61383e59
4 changed files with 86 additions and 3 deletions
|
|
@ -237,10 +237,15 @@ owner discrepancy for bwrap. It is not a claim that every remote backend's exter
|
|||
hosts/templates/tools are packaged or admitted. The ephemeral Messages binding may
|
||||
select an exact runtime path/digest; usual overlap and default-deny checks apply.
|
||||
|
||||
Required `make check`: lint clean, 201 tests passed. Rein's
|
||||
Required `make check`: lint clean, 205 tests passed. Rein's
|
||||
`scripts/prove-metered-runtime.py` consumes the candidate to test installed imports,
|
||||
packaged definitions, explicit one-cycle bootstrap, actual pinned CLI/metered stream,
|
||||
insufficient-capacity refusal and teardown against fixtures. The resulting receipt
|
||||
belongs to `prj-helixforge-factory/evidence/2026-09-09-owner-bootstrap.json`.
|
||||
Production installation and credential-holder/empty-egress profile/placement admission
|
||||
remain SAND-WP-0015-T04 and HFACT-WP-0001-T03/T04; no prior profile is promoted.
|
||||
|
||||
The first standalone candidate exposed a stale cached local wheel despite current
|
||||
Git metadata. Owner builds now refresh all local distributions and compare every
|
||||
installed package source/definition file with its tracked source; stale, missing or
|
||||
extra files refuse the build. Four content-conformance regressions cover that gap.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,33 @@ def install_claude(output: Path, source: Path, sha256: str, version: str) -> dic
|
|||
"expected_version": version, "source": str(source)}
|
||||
|
||||
|
||||
def verify_source_files(site: Path, mappings: list[tuple[Path, str, str]]) -> dict:
|
||||
"""Refuse stale/missing/extra package contents even when versions match."""
|
||||
expected = {}
|
||||
for source, prefix, destination in mappings:
|
||||
tracked = checked(["git", "-C", str(source), "ls-files", prefix]).splitlines()
|
||||
if not tracked:
|
||||
raise ValueError("owner package source mapping is empty")
|
||||
for name in tracked:
|
||||
relative = Path(destination) / Path(name).relative_to(prefix)
|
||||
wanted = hashlib.sha256((source / name).read_bytes()).hexdigest()
|
||||
installed = site / relative
|
||||
if (not installed.is_file() or installed.is_symlink()
|
||||
or hashlib.sha256(installed.read_bytes()).hexdigest() != wanted):
|
||||
raise ValueError(f"owner package content mismatch: {relative}")
|
||||
expected[relative.as_posix()] = wanted
|
||||
actual = set()
|
||||
for package in {Path(name).parts[0] for name in expected}:
|
||||
for path in (site / package).rglob("*"):
|
||||
if path.is_file() and "__pycache__" not in path.parts:
|
||||
actual.add(path.relative_to(site).as_posix())
|
||||
if actual != set(expected):
|
||||
raise ValueError("owner package has unexpected installed files")
|
||||
return {"files_verified": len(expected), "manifest_sha256": hashlib.sha256(
|
||||
json.dumps(expected, sort_keys=True, separators=(",", ":")).encode()
|
||||
).hexdigest()}
|
||||
|
||||
|
||||
def build(output: Path, rein_source: Path, llm_source: Path,
|
||||
claude_binary: Path | None = None, claude_sha256: str | None = None,
|
||||
claude_version: str | None = None, *, owner_runtime: bool = False) -> dict:
|
||||
|
|
@ -68,8 +95,10 @@ def build(output: Path, rein_source: Path, llm_source: Path,
|
|||
output.mkdir(parents=True, exist_ok=False)
|
||||
checked(["/usr/bin/python3", "-m", "venv", "--copies", "--without-pip", str(output)])
|
||||
if owner_runtime:
|
||||
refresh = [arg for name in revisions for arg in ("--refresh-package", name)]
|
||||
checked(["uv", "sync", "--project", str(rein_source), "--frozen", "--no-editable",
|
||||
"--no-dev", "--extra", "glas", "--extra", "llm", "--python", "/usr/bin/python3"],
|
||||
"--no-dev", "--extra", "glas", "--extra", "llm", "--python", "/usr/bin/python3",
|
||||
*refresh],
|
||||
env={**os.environ, "UV_PROJECT_ENVIRONMENT": str(output)})
|
||||
else:
|
||||
checked(["uv", "pip", "install", "--python", str(output / "bin/python3"),
|
||||
|
|
@ -93,6 +122,23 @@ def build(output: Path, rein_source: Path, llm_source: Path,
|
|||
metadata["claude"] = install_claude(output, claude_binary, claude_sha256, claude_version)
|
||||
metadata["source_revisions"] = revisions
|
||||
if owner_runtime:
|
||||
lock = json.loads((rein_source / "deploy/runtime-contract-lock.json").read_text())
|
||||
sources = {x["distribution"]: (rein_source / x["source"]).resolve()
|
||||
for x in lock["dependencies"]}
|
||||
site = Path(checked([str(output / "bin/python3"), "-I", "-B", "-c",
|
||||
"import sysconfig; print(sysconfig.get_path('purelib'))"]))
|
||||
if not site.resolve().is_relative_to(output.resolve()):
|
||||
raise ValueError("owner site-packages is outside artifact")
|
||||
metadata["source_contents"] = verify_source_files(site, [
|
||||
(rein_source, "rein_aharness", "rein_aharness"),
|
||||
(sources["llm-connect"], "llm_connect", "llm_connect"),
|
||||
(sources["glas-harness"], "src/glas_harness", "glas_harness"),
|
||||
(sources["glas-harness"], "profiles", "glas_harness/data/profiles"),
|
||||
(sources["glas-harness"], "registry/reins", "glas_harness/data/reins"),
|
||||
(sources["sandboxer"], "src/sandboxer", "sandboxer"),
|
||||
(sources["sandboxer"], "profiles", "sandboxer/data/profiles"),
|
||||
(sources["sandboxer"], "extensions", "sandboxer/data/extensions"),
|
||||
])
|
||||
metadata["owner_runtime"] = True
|
||||
metadata["runtime_contract"] = lock_report
|
||||
metadata["uv_lock_sha256"] = hashlib.sha256(
|
||||
|
|
|
|||
|
|
@ -69,3 +69,30 @@ def test_owner_build_requires_matching_sibling_lock_before_output(tmp_path, monk
|
|||
with pytest.raises(ValueError, match="does not match"):
|
||||
builder.build(output, rein, llm, owner_runtime=True)
|
||||
assert not output.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("change", [None, "stale", "missing", "extra"])
|
||||
def test_installed_package_contents_must_match_committed_source(tmp_path, change):
|
||||
import subprocess
|
||||
source = tmp_path / "source"
|
||||
package = source / "fixture"
|
||||
package.mkdir(parents=True)
|
||||
(package / "__init__.py").write_text("VERSION = 2\n")
|
||||
subprocess.run(["git", "init", "-q", str(source)], check=True)
|
||||
subprocess.run(["git", "-C", str(source), "add", "fixture"], check=True)
|
||||
site = tmp_path / "site"
|
||||
installed = site / "fixture"
|
||||
installed.mkdir(parents=True)
|
||||
(installed / "__init__.py").write_text("VERSION = 2\n")
|
||||
if change == "stale":
|
||||
(installed / "__init__.py").write_text("VERSION = 1\n")
|
||||
elif change == "missing":
|
||||
(installed / "__init__.py").unlink()
|
||||
elif change == "extra":
|
||||
(installed / "retired.py").write_text("old = True\n")
|
||||
if change:
|
||||
with pytest.raises(ValueError, match="owner package"):
|
||||
builder.verify_source_files(site, [(source, "fixture", "fixture")])
|
||||
else:
|
||||
result = builder.verify_source_files(site, [(source, "fixture", "fixture")])
|
||||
assert result["files_verified"] == 1
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ with non-editable installation and recorded lock/source/package pins. Sand-boxer
|
|||
wheels now carry their profile and extension definitions, fixing a bwrap owner
|
||||
failure that source-checkout tests could hide. The trusted Messages binding can
|
||||
select the digest-pinned runtime without adding an API or profile override.
|
||||
`make check`: lint clean, 201 passed. Existing standalone workload builds remain
|
||||
`make check`: lint clean, 205 passed. Existing standalone workload builds remain
|
||||
supported. See docs/bwrap-runtime.md and rein's docs/owner-bootstrap.md.
|
||||
|
||||
The project's `evidence/2026-09-09-owner-bootstrap.json` records the actual candidate
|
||||
|
|
@ -276,3 +276,8 @@ build and isolated installed-interpreter/CLI proof. T04 remains wait for accepte
|
|||
credential-to-owner delivery, current protected installation and Railiance placement,
|
||||
provider compatibility and G0. Source packaging does not reopen completed T01-T03,
|
||||
T05-T06, broaden the old CCRs or activate the previously installed 2.1.263 artifact.
|
||||
|
||||
The first standalone candidate exposed a stale cached local wheel despite current
|
||||
Git metadata. Owner builds now refresh all local distributions and compare every
|
||||
installed package source/definition file with its tracked source; stale, missing or
|
||||
extra files refuse the build. Four content-conformance regressions cover that gap.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue