SilverSight/scripts/verify_receipt.py
allaun 8e927d8311 fix: receipt verifier, honest layer status, well-formedness witnesses
- scripts/verify_receipt.py: standalone receipt verifier (9 checks:
  schema, canaries, bundle, row count, claim boundary, per-row fields,
  hash self-consistency). Works independently of Lean/pipeline.
- AGENTS.md: anti-smuggle table now honest — Layers 0/3/4 active,
  1/2 noted as manual, 5 partial. No more claims of missing layers.
- Emit.lean: #eval witnesses verify emitManifold contains the correct
  schema string, claim boundary, row count, and bundle validity mark.
- Entry gate updated: Layer 1 -> build, Layer 2 -> emission+verify,
  Layer 4 -> infrastructure (SSH conditional)
2026-06-30 05:52:43 -05:00

116 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""verify_receipt.py — Standalone receipt verifier.
Takes a receipt JSON emitted by rrc-emit-fixture and validates:
1. Schema is avm_rrc_manifold_v1
2. AVM canary receipts all pass
3. Bundle receipt is valid
4. Row count matches total
5. All 278 rows are present (if --full)
6. No row has promotion="promoted" (enforced by claim_boundary)
Usage:
python3 scripts/verify_receipt.py [path_to_receipt.json]
python3 scripts/verify_receipt.py --full [path_to_receipt.json]
"""
import argparse, json, hashlib, sys
from pathlib import Path
def verify(data: dict, full: bool = False) -> list[str]:
errors = []
# 1. Schema check
expected_schema = "avm_rrc_manifold_v1"
if data.get("schema") != expected_schema:
errors.append(f"Schema mismatch: got '{data.get('schema')}', expected '{expected_schema}'")
# 2. Claim boundary
expected_boundary = "admissibility-and-routing-pass-only;not-promoted"
if data.get("claim_boundary") != expected_boundary:
errors.append(f"Claim boundary mismatch: got '{data.get('claim_boundary')}', expected '{expected_boundary}'")
# 3. Canary receipts
if not data.get("avm_canaries_passed", False):
errors.append("AVM canaries failed")
# 4. Bundle receipt
if not data.get("bundle_receipt_valid", False):
errors.append("Bundle receipt invalid")
# 5. Summary
summary = data.get("summary", {})
total = summary.get("total", 0)
passed = summary.get("passed_alignment", 0)
held = summary.get("held", 0)
if total != passed + held:
errors.append(f"Summary mismatch: total={total} != passed={passed} + held={held}")
if full and total != 278:
errors.append(f"Expected 278 rows, got {total}")
# 6. Row validation
rows = data.get("rows", [])
if len(rows) != total:
errors.append(f"Row count mismatch: header says {total}, got {len(rows)}")
if full and total != len(rows):
errors.append(f"Incomplete rows: expected {total}, got {len(rows)}")
# 7. No promoted rows (claim boundary enforcement)
promoted = [r for r in rows if r.get("promotion") == "promoted"]
if promoted:
errors.append(f"{len(promoted)} rows have promotion='promoted' (violates claim_boundary)")
# 8. Each row has required fields
for i, row in enumerate(rows):
eid = row.get("equation_id", f"row_{i}")
if not row.get("equation_id"):
errors.append(f"Row {i}: missing equation_id")
if not row.get("shape"):
errors.append(f"Row {eid}: missing shape")
if not row.get("alignment_status"):
errors.append(f"Row {eid}: missing alignment_status")
# 9. Self-consistency: compute receipt hash
receipt_part = {k: v for k, v in data.items() if k != "receipt_hash"}
canonical = json.dumps(receipt_part, sort_keys=True, separators=(",", ":"))
computed_hash = hashlib.sha256(canonical.encode()).hexdigest()
stored_hash = data.get("receipt_hash", "")
if stored_hash and computed_hash != stored_hash:
errors.append(f"Receipt hash mismatch: computed {computed_hash[:16]}..., stored {stored_hash[:16]}...")
return errors
def main():
parser = argparse.ArgumentParser(description="Verify an AVM receipt JSON")
parser.add_argument("path", nargs="?", default="/tmp/rrc_emit_output.json",
help="Path to receipt JSON")
parser.add_argument("--full", action="store_true", help="Full verification (check all 278 rows)")
args = parser.parse_args()
data = json.loads(Path(args.path).read_text())
errors = verify(data, args.full)
schema = data.get("schema", "?")
canaries = data.get("avm_canaries_passed", False)
bundle = data.get("bundle_receipt_valid", False)
total = data.get("summary", {}).get("total", 0)
passed = data.get("summary", {}).get("passed_alignment", 0)
print(f"Schema: {schema}")
print(f"Canaries: {'' if canaries else ''} {canaries}")
print(f"Bundle: {'' if bundle else ''} {bundle}")
print(f"Rows: {total} total, {passed} passed alignment")
print(f"Verification: {'✅ PASS' if not errors else '❌ FAIL'}")
for e in errors:
print(f" - {e}")
return 0 if not errors else 1
if __name__ == "__main__":
sys.exit(main())