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
|
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.
|
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,
|
`scripts/prove-metered-runtime.py` consumes the candidate to test installed imports,
|
||||||
packaged definitions, explicit one-cycle bootstrap, actual pinned CLI/metered stream,
|
packaged definitions, explicit one-cycle bootstrap, actual pinned CLI/metered stream,
|
||||||
insufficient-capacity refusal and teardown against fixtures. The resulting receipt
|
insufficient-capacity refusal and teardown against fixtures. The resulting receipt
|
||||||
belongs to `prj-helixforge-factory/evidence/2026-09-09-owner-bootstrap.json`.
|
belongs to `prj-helixforge-factory/evidence/2026-09-09-owner-bootstrap.json`.
|
||||||
Production installation and credential-holder/empty-egress profile/placement admission
|
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.
|
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)}
|
"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,
|
def build(output: Path, rein_source: Path, llm_source: Path,
|
||||||
claude_binary: Path | None = None, claude_sha256: str | None = None,
|
claude_binary: Path | None = None, claude_sha256: str | None = None,
|
||||||
claude_version: str | None = None, *, owner_runtime: bool = False) -> dict:
|
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)
|
output.mkdir(parents=True, exist_ok=False)
|
||||||
checked(["/usr/bin/python3", "-m", "venv", "--copies", "--without-pip", str(output)])
|
checked(["/usr/bin/python3", "-m", "venv", "--copies", "--without-pip", str(output)])
|
||||||
if owner_runtime:
|
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",
|
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)})
|
env={**os.environ, "UV_PROJECT_ENVIRONMENT": str(output)})
|
||||||
else:
|
else:
|
||||||
checked(["uv", "pip", "install", "--python", str(output / "bin/python3"),
|
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["claude"] = install_claude(output, claude_binary, claude_sha256, claude_version)
|
||||||
metadata["source_revisions"] = revisions
|
metadata["source_revisions"] = revisions
|
||||||
if owner_runtime:
|
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["owner_runtime"] = True
|
||||||
metadata["runtime_contract"] = lock_report
|
metadata["runtime_contract"] = lock_report
|
||||||
metadata["uv_lock_sha256"] = hashlib.sha256(
|
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"):
|
with pytest.raises(ValueError, match="does not match"):
|
||||||
builder.build(output, rein, llm, owner_runtime=True)
|
builder.build(output, rein, llm, owner_runtime=True)
|
||||||
assert not output.exists()
|
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
|
wheels now carry their profile and extension definitions, fixing a bwrap owner
|
||||||
failure that source-checkout tests could hide. The trusted Messages binding can
|
failure that source-checkout tests could hide. The trusted Messages binding can
|
||||||
select the digest-pinned runtime without adding an API or profile override.
|
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.
|
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
|
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,
|
credential-to-owner delivery, current protected installation and Railiance placement,
|
||||||
provider compatibility and G0. Source packaging does not reopen completed T01-T03,
|
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.
|
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