verify_p3_wp14_fresh_local_engineering.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """Issue a local PASS only after a fresh, bounded regeneration in this process.
  2. The persisted ledger, log, and trace are intentionally unsigned reproducibility
  3. caches. They are useful inputs to this verifier, but never independently
  4. authorize a PASS_LOCAL claim: this entry point always reruns the generator
  5. before reading them again.
  6. """
  7. from __future__ import annotations
  8. import hashlib
  9. import json
  10. import os
  11. import subprocess
  12. import sys
  13. from datetime import datetime, timezone
  14. from pathlib import Path
  15. from generate_p3_wp14_verification_ledger import INPUT_PATHS, ledger_is_fresh
  16. from p3_wp14_secure_io import read_bytes_once, read_json_once
  17. ROOT = Path(__file__).resolve().parents[1]
  18. COMMAND_HOME = ROOT / "work/p3_wp14_fresh_command_home"
  19. PYTHON = ROOT / ".venv/bin/python"
  20. GENERATOR = ROOT / "scripts/generate_p3_wp14_verification_ledger.py"
  21. GENERATOR_TIMEOUT_SECONDS = 360
  22. MANIFEST = ROOT / "docs/phase3/P3_WP14_RELEASE_MANIFEST.json"
  23. LEDGER = ROOT / "docs/validation/P3_WP14_LOCAL_VERIFICATION_LEDGER.json"
  24. LOG = ROOT / "docs/validation/P3_WP14_LOCAL_VERIFICATION.log"
  25. TRACE = ROOT / "docs/phase3/P3_WP14_RELEASE_TRACEABILITY.json"
  26. CASES = ROOT / "docs/acceptance/P3_WP14_UAT_CASES.json"
  27. STATE = ROOT / "docs/validation/P3_WP14_VERIFICATION_RUN_STATE.json"
  28. def _sha256(path: Path) -> str:
  29. return hashlib.sha256(read_bytes_once(path)).hexdigest()
  30. def _minimal_command_env() -> dict[str, str]:
  31. return {
  32. "PATH": os.environ.get("PATH", ""),
  33. "HOME": str(COMMAND_HOME),
  34. "TMPDIR": str(COMMAND_HOME / "tmp"),
  35. "DOCKER_CONFIG": str(COMMAND_HOME / "docker"),
  36. "PYTHONDONTWRITEBYTECODE": "1",
  37. "LANG": "C.UTF-8",
  38. "LC_ALL": "C.UTF-8",
  39. }
  40. def _run_generator() -> str:
  41. completed = subprocess.run(
  42. [str(PYTHON), str(GENERATOR)],
  43. cwd=ROOT,
  44. env=_minimal_command_env(),
  45. text=True,
  46. capture_output=True,
  47. check=False,
  48. timeout=GENERATOR_TIMEOUT_SECONDS,
  49. )
  50. if completed.returncode != 0:
  51. raise RuntimeError("fresh local ledger generator failed")
  52. state = read_json_once(STATE)
  53. if not isinstance(state, dict) or state.get("status") != "COMPLETE" or not isinstance(state.get("run_nonce"), str):
  54. raise RuntimeError("generator did not produce a complete session state")
  55. return state["run_nonce"]
  56. def _assert_current_expiry(payload: dict, now: datetime) -> None:
  57. if not ledger_is_fresh(payload, now):
  58. raise ValueError("unsigned reproducibility cache is expired")
  59. def _expected_case_ids() -> set[str]:
  60. cases = read_json_once(CASES)
  61. if not isinstance(cases, dict) or not isinstance(cases.get("cases"), list):
  62. raise ValueError("invalid acceptance case matrix")
  63. return {case["id"] for case in cases["cases"] if case.get("result") == "PASS_LOCAL"}
  64. def _verify_cache(now: datetime, *, expected_nonce: str | None = None) -> None:
  65. ledger = read_json_once(LEDGER)
  66. trace = read_json_once(TRACE)
  67. state = read_json_once(STATE)
  68. if not isinstance(ledger, dict) or not isinstance(trace, dict) or not isinstance(state, dict):
  69. raise ValueError("invalid unsigned cache format")
  70. if state.get("status") != "COMPLETE" or state.get("run_nonce") != ledger.get("run_nonce"):
  71. raise ValueError("ledger state does not identify a complete session")
  72. if expected_nonce is not None and ledger.get("run_nonce") != expected_nonce:
  73. raise ValueError("fresh verifier session nonce mismatch")
  74. if ledger.get("cache_class") != "UNSIGNED_REPRODUCIBILITY_CACHE":
  75. raise ValueError("ledger is not explicitly an unsigned cache")
  76. if ledger.get("pass_local_authority") != "FRESH_VERIFIER_CURRENT_PROCESS_ONLY":
  77. raise ValueError("ledger must not independently grant PASS_LOCAL")
  78. anchor = ledger.get("external_signature_anchor")
  79. if anchor != {"status": "TBD_EXTERNAL", "gate": "BLOCKED_EXTERNAL"}:
  80. raise ValueError("external signature boundary is not closed")
  81. _assert_current_expiry(ledger, now)
  82. if trace.get("release_manifest_sha256") != _sha256(MANIFEST):
  83. raise ValueError("manifest digest mismatch")
  84. if trace.get("verification_ledger_sha256") != _sha256(LEDGER):
  85. raise ValueError("ledger digest mismatch")
  86. if trace.get("verification_log_sha256") != _sha256(LOG):
  87. raise ValueError("verification log digest mismatch")
  88. if trace.get("local_verification_cache") != {
  89. "classification": "UNSIGNED_REPRODUCIBILITY_CACHE",
  90. "pass_local_authority": "FRESH_VERIFIER_CURRENT_PROCESS_ONLY",
  91. "external_signature_status": "TBD_EXTERNAL",
  92. "enterprise_gate": "BLOCKED_EXTERNAL",
  93. }:
  94. raise ValueError("trace does not preserve unsigned-cache boundary")
  95. controlled = {path: _sha256(ROOT / path) for path in ledger.get("controlled_input_paths", [])}
  96. controlled_digest = hashlib.sha256(
  97. json.dumps(controlled, sort_keys=True, separators=(",", ":")).encode("utf-8")
  98. ).hexdigest()
  99. if ledger.get("controlled_input_paths") != list(INPUT_PATHS) or ledger.get("controlled_input_sha256") != controlled_digest:
  100. raise ValueError("controlled input closure drift")
  101. if ledger.get("log_sha256") != _sha256(LOG):
  102. raise ValueError("verification log digest mismatch")
  103. case_ids = {case_id for run in ledger.get("runs", []) for case_id in run.get("case_ids", [])}
  104. if case_ids != _expected_case_ids():
  105. raise ValueError("PASS_LOCAL case mapping drift")
  106. for run in ledger.get("runs", []):
  107. dependencies = run.get("dependency_sha256")
  108. if run.get("status") != "PASS" or run.get("exit_code") != 0 or not isinstance(dependencies, dict):
  109. raise ValueError("bounded local command did not pass")
  110. current = {path: _sha256(ROOT / path) for path in dependencies}
  111. aggregate = hashlib.sha256(
  112. json.dumps(current, sort_keys=True, separators=(",", ":")).encode("utf-8")
  113. ).hexdigest()
  114. if dependencies != current or run.get("dependency_aggregate_sha256") != aggregate:
  115. raise ValueError("command dependency closure drift")
  116. def main(argv: list[str] | None = None) -> int:
  117. arguments = [] if argv is None else argv
  118. if set(arguments) - {"--verify-existing"} or len(arguments) > 1:
  119. print("usage: verify_p3_wp14_fresh_local_engineering.py [--verify-existing]", file=sys.stderr)
  120. return 2
  121. try:
  122. generated_nonce = None
  123. if not arguments:
  124. generated_nonce = _run_generator()
  125. _verify_cache(datetime.now(timezone.utc), expected_nonce=generated_nonce)
  126. except (subprocess.TimeoutExpired, Exception) as error:
  127. print(f"P3-WP14 fresh local verification failed: {type(error).__name__}", file=sys.stderr)
  128. return 1
  129. if arguments:
  130. print("P3-WP14 VERIFY_EXISTING_DIAGNOSTIC_ONLY (cannot grant PASS_LOCAL)")
  131. else:
  132. print("P3-WP14 PASS_LOCAL_CURRENT_SESSION (unsigned local reproducibility only)")
  133. return 0
  134. if __name__ == "__main__":
  135. sys.exit(main(sys.argv[1:]))