from __future__ import annotations import hashlib import json from pathlib import Path import sys import tempfile import unittest ROOT = Path(__file__).parents[1] sys.path.insert(0, str(ROOT / "tools")) from verify_release import verify COMMIT = "1" * 40 SOURCE_DIGEST = "2" * 64 def _page(revision: str = COMMIT, digest: str = SOURCE_DIGEST) -> str: return ( '' ) def _release(root: Path) -> Path: build = root / "build" canonical = build / "standards/example/v1/index.html" revision = build / "standards/example/v1/revisions/draft-1/index.html" canonical.parent.mkdir(parents=True) revision.parent.mkdir(parents=True) canonical.write_text(_page(), encoding="utf-8") revision.write_text(_page(), encoding="utf-8") (build / "index.html").write_text("", encoding="utf-8") manifest = { "schema_version": 1, "generated_as_of": "2026-08-18", "documents": [ { "id": "example", "title": "Example Standard", "status": "proposed", "revision": "draft-1", "owner": "example-owner", "last_reviewed": "2026-08-18", "review_due": "2027-02-18", "canonical_path": "standards/example/v1/index.html", "revision_path": "standards/example/v1/revisions/draft-1/index.html", "source_repo": "canon", "source_path": "canon/standards/example.md", "source_revision": COMMIT, "source_digest": SOURCE_DIGEST, } ], } (build / "publication-manifest.json").write_text( json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8" ) (build / "source-inventory.json").write_text( json.dumps( { "schema_version": "policy-nexus-source-inventory/v1", "source_set_digest": "3" * 64, "repositories": [ {"name": "canon", "revision": COMMIT, "source_count": 1} ], "sources": [ { "source_repo": "canon", "source_path": "canon/standards/example.md", "disposition": "published", "reason": "test", } ], }, sort_keys=True, ) + "\n", encoding="utf-8", ) return build class ReleaseVerificationTest(unittest.TestCase): def test_release_pipeline_does_not_delete_immutable_revision_tree(self) -> None: makefile = (ROOT / "Makefile").read_text(encoding="utf-8") containerfile = (ROOT / "Containerfile").read_text(encoding="utf-8") self.assertIn("release-build: build release-check", makefile) self.assertNotIn("RUN rm -rf build", containerfile) def test_accepts_clean_provenance_and_returns_manifest_digest(self) -> None: with tempfile.TemporaryDirectory() as directory: build = _release(Path(directory)) evidence = verify(build) expected = hashlib.sha256( (build / "publication-manifest.json").read_bytes() ).hexdigest() self.assertEqual(expected, evidence["publication_manifest_digest"]) self.assertEqual(["example"], evidence["documents"]) self.assertEqual("3" * 64, evidence["source_set_digest"]) def test_rejects_working_tree_source_revision(self) -> None: with tempfile.TemporaryDirectory() as directory: build = _release(Path(directory)) manifest_path = build / "publication-manifest.json" manifest = json.loads(manifest_path.read_text(encoding="utf-8")) dirty = COMMIT + "+working-tree." + SOURCE_DIGEST[:12] manifest["documents"][0]["source_revision"] = dirty manifest_path.write_text(json.dumps(manifest), encoding="utf-8") canonical = build / manifest["documents"][0]["canonical_path"] canonical.write_text(_page(revision=dirty), encoding="utf-8") with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"): verify(build) def test_rejects_immutable_revision_built_from_dirty_source(self) -> None: with tempfile.TemporaryDirectory() as directory: build = _release(Path(directory)) revision = build / "standards/example/v1/revisions/draft-1/index.html" revision.write_text( _page(revision=COMMIT + "+working-tree." + SOURCE_DIGEST[:12]), encoding="utf-8", ) with self.assertRaisesRegex(ValueError, "immutable revision page"): verify(build) def test_rejects_page_digest_mismatch(self) -> None: with tempfile.TemporaryDirectory() as directory: build = _release(Path(directory)) canonical = build / "standards/example/v1/index.html" canonical.write_text(_page(digest="3" * 64), encoding="utf-8") with self.assertRaisesRegex(ValueError, "provenance differs"): verify(build) if __name__ == "__main__": unittest.main()