autonomy-smoke-tests V2R2C2 release-runtime binding smoke and offline package preflight PASS
autonomy-unit-tests 782 passed, 3 skipped, 154 subtests passed on source branch
git-diff-check governed source diff-check PASS against merged main f1695ce
py-compile all tracked Python source compile PASS
reconciliation-contract completed-replay reconciliation contract preserved; 11 focused tests PASS
secret-scan narrow source and deployable payload secret scan PASS; no credentials included
signer-validator-contract trusted release plan/adapter contract 48 tests PASS; formal Owner signature not asserted here
campaign-approval-gate V2R2C2 candidate binding contract PASS; production approval remains formal-envelope gated
release-state-machine V2R2C2 plan-driven release state contract PASS; no production mutation performed
91 lines
6.1 KiB
Python
91 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Build a deterministic plan-bound production payload from one clean source tree."""
|
|
from __future__ import annotations
|
|
import argparse, hashlib, json, shutil, tarfile, tempfile
|
|
from pathlib import Path
|
|
|
|
SOURCE_FILES = (
|
|
"active-runtime/app/deployment_state.py",
|
|
"active-runtime/app/v2r1c6_privacy_tools.py",
|
|
"active-runtime/app/v2r2c2_health_service.py",
|
|
"deploy/aios-v2/systemd/aios-v2r2c2-production.service",
|
|
)
|
|
|
|
def canon(value: object) -> bytes:
|
|
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
|
|
|
def digest(path: Path) -> str:
|
|
h = hashlib.sha256()
|
|
with path.open("rb") as f:
|
|
for block in iter(lambda: f.read(1024 * 1024), b""): h.update(block)
|
|
return h.hexdigest()
|
|
|
|
def require_oid(value: str, label: str) -> str:
|
|
if len(value) != 40 or any(c not in "0123456789abcdef" for c in value): raise SystemExit(label + "_invalid")
|
|
return value
|
|
|
|
def require_sha(value: str, label: str) -> str:
|
|
if len(value) != 64 or any(c not in "0123456789abcdef" for c in value): raise SystemExit(label + "_invalid")
|
|
return value
|
|
|
|
def build(source: Path, output: Path, *, version: str, source_oid: str, tree_oid: str,
|
|
rollback_version: str, rollback_target_oid: str, rollback_artifact_sha256: str,
|
|
service: str, port: int) -> dict[str, object]:
|
|
if not source.is_dir() or source.is_symlink(): raise SystemExit("source_directory_invalid")
|
|
require_oid(source_oid, "source_oid"); require_oid(tree_oid, "tree_oid")
|
|
require_oid(rollback_target_oid, "rollback_target_oid"); require_sha(rollback_artifact_sha256, "rollback_artifact_sha256")
|
|
if version != "V2R2C2" or rollback_version == version: raise SystemExit("release_version_binding_invalid")
|
|
if service != "aios-v2r2c2-production.service" or port != 18928: raise SystemExit("adapter_policy_binding_invalid")
|
|
if output.exists(): raise SystemExit("output_exists")
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
with tempfile.TemporaryDirectory(prefix="trusted-release-build-") as td:
|
|
payload = Path(td) / "payload"; payload.mkdir()
|
|
for rel in SOURCE_FILES:
|
|
source_file = source / rel
|
|
if not source_file.is_file() or source_file.is_symlink(): raise SystemExit("required_source_missing:" + rel)
|
|
dest = payload / rel; dest.parent.mkdir(parents=True, exist_ok=True); shutil.copyfile(source_file, dest)
|
|
(payload / "VERSION").write_text(version + "\n", encoding="utf-8")
|
|
build_meta = {"schema": "aios.trusted-release-artifact-build.v1", "version": version,
|
|
"source_commit": source_oid, "tree_oid": tree_oid, "production_deployed": False,
|
|
"tag_created": False, "baseline_changed": False, "adapter_service": service, "adapter_port": port}
|
|
(payload / "BUILD-METADATA.json").write_bytes(canon(build_meta))
|
|
rollback = {"schema": "aios.trusted-release-rollback.v1", "version": rollback_version,
|
|
"target_oid": rollback_target_oid, "artifact_sha256": rollback_artifact_sha256,
|
|
"automatic_rollback": False}
|
|
(payload / "ROLLBACK-METADATA.json").write_bytes(canon(rollback))
|
|
files = []
|
|
for item in sorted(p for p in payload.rglob("*") if p.is_file()):
|
|
if item.name != "ARTIFACT-MANIFEST.json":
|
|
files.append({"path": "payload/" + item.relative_to(payload).as_posix(), "sha256": digest(item), "size": item.stat().st_size})
|
|
manifest = {"schema": "aios.trusted-release-artifact-manifest.v1", "version": version,
|
|
"source_commit": source_oid, "tree_oid": tree_oid, "files": files,
|
|
"deployable": True, "production_deployed": False}
|
|
(payload / "ARTIFACT-MANIFEST.json").write_bytes(canon(manifest))
|
|
files.append({"path": "payload/ARTIFACT-MANIFEST.json", "sha256": digest(payload / "ARTIFACT-MANIFEST.json"), "size": (payload / "ARTIFACT-MANIFEST.json").stat().st_size})
|
|
tar_path = Path(td) / "artifact.tar"
|
|
with tarfile.open(tar_path, "w") as archive:
|
|
for item in sorted(p for p in payload.rglob("*") if p.is_file()):
|
|
info = archive.gettarinfo(str(item), arcname="payload/" + item.relative_to(payload).as_posix())
|
|
info.uid = 0; info.gid = 0; info.uname = "root"; info.gname = "root"; info.mtime = 0; info.mode = 0o644
|
|
with item.open("rb") as stream: archive.addfile(info, stream)
|
|
shutil.copyfile(tar_path, output)
|
|
receipt = {"schema": "aios.trusted-release-artifact-receipt.v1", "version": version,
|
|
"source_commit": source_oid, "tree_oid": tree_oid, "artifact_sha256": digest(output),
|
|
"artifact_size": output.stat().st_size, "manifest_sha256": hashlib.sha256(canon(manifest)).hexdigest(),
|
|
"package_closure_sha256": hashlib.sha256(canon({"source_commit": source_oid, "tree_oid": tree_oid, "files": files})).hexdigest(),
|
|
"production_deployed": False, "tag_created": False, "baseline_changed": False,
|
|
"service": service, "port": port}
|
|
output.with_suffix(".manifest.json").write_bytes(canon(receipt))
|
|
return receipt
|
|
|
|
def main() -> int:
|
|
p = argparse.ArgumentParser(); p.add_argument("--source", type=Path, required=True); p.add_argument("--output", type=Path, required=True)
|
|
p.add_argument("--version", required=True); p.add_argument("--source-oid", required=True); p.add_argument("--tree-oid", required=True)
|
|
p.add_argument("--rollback-version", required=True); p.add_argument("--rollback-target-oid", required=True); p.add_argument("--rollback-artifact-sha256", required=True)
|
|
p.add_argument("--service", required=True); p.add_argument("--port", type=int, required=True); a = p.parse_args()
|
|
result = build(a.source, a.output, version=a.version, source_oid=a.source_oid, tree_oid=a.tree_oid,
|
|
rollback_version=a.rollback_version, rollback_target_oid=a.rollback_target_oid,
|
|
rollback_artifact_sha256=a.rollback_artifact_sha256, service=a.service, port=a.port)
|
|
print(json.dumps(result, sort_keys=True)); return 0
|
|
if __name__ == "__main__": raise SystemExit(main())
|