import json from audit_core.ingestion import IngestionApplication from audit_core.integrity import GENESIS, chain_link, load_attestation, write_attestation from audit_core.interface import AuditEvent from audit_core.sqlite_backend import SQLiteAuditBackend from test_ingestion import invoke def _event(event_id: str, **kw) -> AuditEvent: fields = dict( event_id=event_id, source="user-engine", action="membership.added", resource="membership-1", outcome="recorded", tenant="tenant:friendly:binky", scope="tenant", details={"correlation_id": "corr-1"}, observed_at="2026-08-09T00:00:00+00:00", ) fields.update(kw) return AuditEvent(**fields) def _digest(event: AuditEvent) -> str: import hashlib return hashlib.sha256( json.dumps(event.as_record(), sort_keys=True).encode() ).hexdigest() def test_first_accept_sets_genesis(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) event = _event("e1") backend.accept(event, _digest(event)) row = backend.db.execute( "SELECT chain_prev, chain_hash FROM events WHERE event_id = 'e1'" ).fetchone() assert row[0] == GENESIS assert row[1] == chain_link(GENESIS, _digest(event), "e1") def test_second_accept_links(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) first = _event("e1") second = _event("e2") backend.accept(first, _digest(first)) backend.accept(second, _digest(second)) head = backend.db.execute( "SELECT chain_hash FROM events WHERE event_id = 'e1'" ).fetchone()[0] prev = backend.db.execute( "SELECT chain_prev FROM events WHERE event_id = 'e2'" ).fetchone()[0] assert prev == head def test_duplicate_does_not_fork(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) event = _event("e1") digest = _digest(event) assert backend.accept(event, digest).duplicate is False assert backend.accept(event, digest).duplicate is True count = backend.db.execute("SELECT count(*) FROM events").fetchone()[0] assert count == 1 assert backend.verify_chain().events == 1 def test_verify_clean_on_fresh_store(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) empty = backend.verify_chain() assert empty.intact is True assert empty.events == 0 assert empty.head == GENESIS first = _event("e1") second = _event("e2") backend.accept(first, _digest(first)) backend.accept(second, _digest(second)) report = backend.verify_chain() assert report.intact is True assert report.events == 2 assert report.first_break is None def test_rewritten_payload_fails_verify(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) first = _event("e1") second = _event("e2") backend.accept(first, _digest(first)) backend.accept(second, _digest(second)) backend.db.execute("UPDATE events SET payload_hash = 'deadbeef' WHERE event_id = 'e2'") report = backend.verify_chain() assert report.intact is False assert report.first_break == "e2" def test_attestation_mismatch(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) event = _event("e1") backend.accept(event, _digest(event)) path = tmp_path / "head.json" write_attestation(path, backend.verify_chain()) cited = load_attestation(path) assert backend.verify_chain(cited).attestation_match is True cited["chain_hash"] = "f" * 64 broken = backend.verify_chain(cited) assert broken.intact is False assert broken.first_break == "attestation_mismatch" assert broken.attestation_match is False def test_attestation_still_matches_after_growth(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) first = _event("e1") backend.accept(first, _digest(first)) cited = backend.attest_chain() second = _event("e2") backend.accept(second, _digest(second)) report = backend.verify_chain(cited) assert report.intact is True assert report.attestation_match is True assert report.events == 2 def test_http_integrity_requires_read(tmp_path): backend = SQLiteAuditBackend(str(tmp_path / "c.db")) app = IngestionApplication(backend, "opaque") status, _ = invoke(app, None, path="/v1/integrity", method="GET", body=b"") assert status.startswith("200") event = _event("e1") backend.accept(event, _digest(event)) status, body = invoke(app, None, path="/v1/integrity", method="GET", body=b"") assert status.startswith("200") assert body["intact"] is True assert body["events"] == 1 assert "record" not in body