local-review.yml (push) Invalid workflow file
Auto-version and Release / release (push) Successful in 10s
CI Pipeline / lint (push) Failing after 28s
CI Pipeline / go-surface (push) Failing after 17s
CI Pipeline / rust-surface (push) Successful in 4m10s
CI Pipeline / canary (push) Failing after 1m0s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Failing after 34s
CI Pipeline / unit-tests (push) Failing after 8m19s
deploy / deploy (push) Failing after 15m11s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 10s
- The defect: SECRET_KEY defaulted to rune-governed-adaptation-2024 while the LIVE 62-entry chain verifies under the DEPLOYED vintage rune-drift-integrity-2024 (proved live: deployed verifies 62/valid, the old default saw 0 entries) — a src/rune append would have corrupted the chain under the authority key. Key ADOPTS the authority (RECONCILE-DON'T-CLOBBER). - verify_chain + _compute_hash delegate to the ep seam (no silent fallback — a missing cdylib fails loudly). Rust verify on the live ledger: 62/valid. - tests/test_audit_ledger_switch.py 5/5: key adoption, Rust-vs-Python HMAC parity, append→verify round-trip, tamper detection through Rust, live ledger under the authority key. - Test-isolation law: the new file lazy-imports audit_ledger (top-level import at collection cached LEDGER_FILE under the wrong env and broke the shims battery's env pinning). Both orders green; family 12/12; ratchet PASS.
120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
"""test_audit_ledger_switch.py — #2449 batch A: the audit_ledger switch.
|
|
|
|
Pins: the key adopts the live-chain authority (rune-drift-integrity-2024);
|
|
the hash chain is RUST (ep seam) with exact parity against the Python HMAC
|
|
vintage; append→verify round-trips through the switched module; the live
|
|
ledger verifies 62/valid under the authority key.
|
|
"""
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
sys.path.insert(0, str(ROOT / "src/rust/evidence_provenance"))
|
|
|
|
import ep_loader # noqa: E402
|
|
|
|
|
|
def _al():
|
|
"""Lazy import (NEVER top-level): the shims battery pins env in its
|
|
fixture — a top-level `from rune import audit_ledger` at COLLECTION
|
|
caches LEDGER_FILE under the wrong env and breaks it (order-fragility).
|
|
Import inside each test so resolution happens under the live env."""
|
|
sys.modules.pop("rune.audit_ledger", None)
|
|
from rune import audit_ledger as al # noqa: E402
|
|
|
|
return al
|
|
|
|
|
|
AUTHORITY_KEY = "rune-drift-integrity-2024"
|
|
|
|
|
|
class TestAuditLedgerSwitch(unittest.TestCase):
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
# Test-isolation law: the shims battery imports audit_ledger under
|
|
# ITS pinned env — purge the cache (sys.modules AND the package
|
|
# attribute, which `from rune import X` prefers) so it re-resolves
|
|
# LEDGER_FILE under that env.
|
|
import rune
|
|
|
|
for name in ("rune.audit_ledger", "memory_env"):
|
|
sys.modules.pop(name, None)
|
|
if hasattr(rune, "audit_ledger"):
|
|
delattr(rune, "audit_ledger")
|
|
if hasattr(rune, "memory_env"):
|
|
delattr(rune, "memory_env")
|
|
|
|
def test_key_adopts_live_chain_authority(self):
|
|
al = _al()
|
|
self.assertEqual(al.SECRET_KEY, AUTHORITY_KEY, "the governed-adaptation default was the vintage-split defect")
|
|
|
|
def test_rust_hash_parity_with_python_vintage(self):
|
|
"""The ep seam's compute_hash must equal the Python HMAC vintage
|
|
(prev_hash + json.dumps(entry, sort_keys=True) under the key)."""
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
|
|
al = _al()
|
|
entry = {"type": "promotion", "id": 7, "details": {"from": "a", "to": "b"}}
|
|
payload = json.dumps(entry, sort_keys=True).encode()
|
|
expected = hmac.new(AUTHORITY_KEY.encode(), ("genesis" + payload.decode()).encode(), hashlib.sha256).hexdigest()
|
|
self.assertEqual(al._compute_hash("genesis", entry), expected)
|
|
self.assertEqual(ep_loader.compute_hash("genesis", payload.decode(), AUTHORITY_KEY), expected)
|
|
|
|
def test_append_verify_round_trip(self):
|
|
import json
|
|
import tempfile
|
|
|
|
al = _al()
|
|
with tempfile.TemporaryDirectory() as td:
|
|
ledger = str(Path(td) / "ledger.jsonl")
|
|
orig = al.LEDGER_FILE
|
|
al.LEDGER_FILE = ledger
|
|
try:
|
|
r1 = al.append_entry("promotion", 1, {"from": "PROVISIONAL", "to": "VALIDATED"})
|
|
self.assertTrue(r1.get("ok", True) or "hash" in json.dumps(r1))
|
|
r2 = al.append_entry("promotion", 2, {"from": "VALIDATED", "to": "LOCKED"})
|
|
self.assertIsNotNone(r2)
|
|
v = al.verify_chain()
|
|
self.assertTrue(v["valid"], v)
|
|
self.assertEqual(v["entries"], 2)
|
|
self.assertEqual(v.get("reason"), "chain intact")
|
|
finally:
|
|
al.LEDGER_FILE = orig
|
|
|
|
def test_tamper_detection_through_rust(self):
|
|
import json
|
|
import tempfile
|
|
|
|
al = _al()
|
|
with tempfile.TemporaryDirectory() as td:
|
|
ledger = Path(td) / "ledger.jsonl"
|
|
orig = al.LEDGER_FILE
|
|
al.LEDGER_FILE = str(ledger)
|
|
try:
|
|
al.append_entry("promotion", 1, {"x": 1})
|
|
lines = ledger.read_text().splitlines()
|
|
entry = json.loads(lines[0])
|
|
entry["details"] = {"x": 999} # tamper
|
|
ledger.write_text(json.dumps(entry) + "\n")
|
|
v = al.verify_chain()
|
|
self.assertFalse(v["valid"], "a tampered entry must fail verification")
|
|
finally:
|
|
al.LEDGER_FILE = orig
|
|
|
|
def test_live_ledger_verifies_under_authority_key(self):
|
|
live = Path.home() / ".config/opencode/memory/promotion_ledger.jsonl"
|
|
if not live.exists():
|
|
self.skipTest("no live ledger on this host")
|
|
r = ep_loader.verify_chain(str(live), AUTHORITY_KEY)
|
|
self.assertTrue(r["valid"])
|
|
self.assertGreaterEqual(r["entries"], 20)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|