Persist review evidence and deliver audit records transactionally

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-10 23:27:19 +02:00
parent 0e48355b9f
commit 2cc32168ac
14 changed files with 1474 additions and 22 deletions

View file

@ -0,0 +1,112 @@
from datetime import datetime, timezone
import json
import pytest
from informed_decision.audit import AuditCoreSink, AuditDeliveryError, OutboxWorker
from informed_decision.http_transport import TransportError
from informed_decision.store import Store
from test_durable_store import storage, present
class Replies:
def __init__(self, *replies):
self.replies = list(replies)
self.calls = []
def request(self, method, url, **kwargs):
self.calls.append((method, url, kwargs))
reply = self.replies.pop(0)
if isinstance(reply, Exception): raise reply
return reply
@pytest.mark.parametrize("status,name", [(202, "accepted"), (200, "duplicate")])
def test_only_confirmed_receiver_receipt_marks_delivered(storage, status, name):
store, memo = storage
present(store, memo)
event = store.evidence()[0]
transport = Replies((status, {"status": name, "reference": "audit:" + event["id"]}))
sink = AuditCoreSink("https://audit.test", lambda: "synthetic-token", transport=transport)
assert OutboxWorker(store, sink).run_once()["delivered"] == 1
row = Store(store.path).outbox()[0]
assert row["state"] == "delivered" and row["receiver_reference"] == "audit:" + event["id"]
method, url, params = transport.calls[0]
assert (method, url) == ("POST", "https://audit.test/v1/events")
assert params["headers"]["Idempotency-Key"] == event["id"]
assert params["body"] == event["envelope"].encode()
assert "synthetic-token" not in event["envelope"]
@pytest.mark.parametrize("reply,code,blocked", [
((400, {"error": "secret-sentinel"}), "rejected", True),
((401, {}), "unauthorized", True), ((403, {}), "unauthorized", True),
((409, {}), "conflict", True), ((503, {}), "unavailable", False),
((500, {}), "unavailable", False), (TransportError("secret-sentinel"), "unavailable", False),
((202, {"status": "accepted", "reference": "audit:other-event"}), "invalid_receipt", False),
((200, {"status": "ok", "reference": "audit:fixture"}), "invalid_receipt", False),
])
def test_refusals_preserve_pending_evidence_with_bounded_error(storage, reply, code, blocked):
store, memo = storage
present(store, memo)
before = store.evidence()
transport = Replies(reply)
worker = OutboxWorker(store, AuditCoreSink("https://audit.test", lambda: "synthetic-token", transport=transport))
result = worker.run_once()
assert result["blocked" if blocked else "retrying"] == 1
row = Store(store.path).outbox()[0]
assert row["state"] == ("blocked" if blocked else "pending")
assert row["last_error"] == code and "secret-sentinel" not in str(row)
assert store.evidence() == before
assert worker.run_once() == {"delivered": 0, "retrying": 0, "blocked": 0}
def test_repaired_credential_requeues_original_bytes(storage):
store, memo = storage
present(store, memo)
event = store.evidence()[0]
transport = Replies((401, {}), (202, {"status": "accepted", "reference": "audit:" + event["id"]}))
tokens = iter(["old-synthetic", "new-synthetic"])
worker = OutboxWorker(store, AuditCoreSink("https://audit.test", lambda: next(tokens), transport=transport))
assert worker.run_once()["blocked"] == 1
store.requeue_blocked(event["id"])
assert worker.run_once()["delivered"] == 1
assert transport.calls[0][2]["body"] == transport.calls[1][2]["body"]
assert transport.calls[1][2]["headers"]["Authorization"] == "Bearer new-synthetic"
def test_unavailable_policy_record_is_never_a_human_decline(storage):
store, memo = storage
store.record_unreachable(memo.id, "access-engine")
envelope = json.loads(store.evidence()[0]["envelope"])
assert envelope["type"] == "informed-decision.stance_application"
assert envelope["data"]["stance_applied"] == "fail_closed"
assert envelope["data"]["decision_attributable"] is False
assert "verb" not in envelope["data"]
def test_reconciliation_reports_gap_without_claiming_completeness(storage):
store, memo = storage
present(store, memo)
since, until = "2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z"
remote = {"source": "informed-decision", "tenant": "tenant:platform", "since": since, "until": until, "counts": []}
transport = Replies((200, remote))
worker = OutboxWorker(store, AuditCoreSink("https://audit.test", lambda: "synthetic-token", transport=transport))
report = worker.reconcile(since, until)
assert report["count_values_match"] is False
assert report["counts"]["informed-decision.presentation"] == {"source": 1, "receiver": 0}
assert report["completeness_proven"] is report["reconstructability_proven"] is False
assert report["source_time_basis"] == "occurred_at" and report["receiver_time_basis"] == "accepted_at"
assert report["automatic_loss_finding"] is False
with pytest.raises(ValueError): worker.reconcile(None, until)
@pytest.mark.parametrize("change", [{"source": "other-source"}, {"tenant": "other-tenant"},
{"since": "2019-01-01T00:00:00Z"}, {"counts": [{"class": "presentation", "count": True}]},
{"counts": [{"class": "presentation", "count": -1}]},
{"counts": [{"class": "presentation", "count": 1}, {"class": "presentation", "count": 1}]}])
def test_reconciliation_rejects_wrong_scope_window_or_shape(change):
body = {"source": "informed-decision", "tenant": "tenant:platform", "since": "2020-01-01T00:00:00Z",
"until": "2100-01-01T00:00:00Z", "counts": [], **change}
sink = AuditCoreSink("https://audit.test", lambda: "synthetic-token", transport=Replies((200, body)))
with pytest.raises(AuditDeliveryError): sink.counts("2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z")

View file

@ -0,0 +1,143 @@
"""Actual Approval Engine and Audit Core APIs, with synthetic identities/custody."""
import io
import json
import os
from pathlib import Path
import sys
from urllib.parse import urlsplit
import pytest
from informed_decision.audit import AuditCoreSink, AuditDeliveryError, OutboxWorker
from informed_decision.disposition import Actor, ActorKind, Verb
from informed_decision.http_transport import TransportError
from informed_decision.memo import PacketItem
from informed_decision.store import Store, Conflict
from test_approval_component import component, signing_key
from test_skeleton import make_memo
from test_durable_store import storage, present
@pytest.fixture
def receiver(tmp_path):
source = os.environ.get("INFD_AUDIT_CORE_SOURCE")
if not source:
pytest.skip("set INFD_AUDIT_CORE_SOURCE for actual Audit Core contract checks")
assert (Path(source) / "audit_core/ingestion.py").is_file()
sys.path.insert(0, source)
from audit_core.ingestion import IngestionApplication
from audit_core.senders import SenderIdentity, SenderRegistry
from audit_core.sqlite_backend import SQLiteAuditBackend
backend = SQLiteAuditBackend(str(tmp_path / "independent-receiver.sqlite"))
registry = SenderRegistry([SenderIdentity(name="informed-decision", tokens=("synthetic-audit-token",),
sources=frozenset({"informed-decision"}), tenants=frozenset({"tenant:platform"}),
evidence_kind="load-bearing", secret_policy="redact", may_read=False),
SenderIdentity(name="independent-fixture-reader", tokens=("synthetic-auditor-token",),
sources=frozenset({"informed-decision"}), tenants=frozenset({"tenant:platform"}),
may_read=True, may_write=False)])
app = IngestionApplication(backend, registry)
class Transport:
lose_reply = False
def request(self, method, url, *, headers=None, body=None):
parsed = urlsplit(url)
environ = {"PATH_INFO": parsed.path, "QUERY_STRING": parsed.query, "REQUEST_METHOD": method,
"CONTENT_LENGTH": str(len(body or b"")), "wsgi.input": io.BytesIO(body or b""),
"HTTP_AUTHORIZATION": headers.get("Authorization", ""),
"HTTP_IDEMPOTENCY_KEY": headers.get("Idempotency-Key", "")}
result = {}
out = b"".join(app(environ, lambda status, headers: result.update(status=int(status.split()[0]))))
if self.lose_reply:
self.lose_reply = False
raise TransportError("simulated lost receiver response")
return result["status"], json.loads(out)
transport = Transport()
sink = AuditCoreSink("https://audit.test", lambda: "synthetic-audit-token", transport=transport)
return sink, transport, backend
def durable_setup(tmp_path, component):
client, engine, transport, session, _ = component
private = tmp_path / "holder"
private.mkdir(mode=0o700)
store = Store(private / "review.sqlite")
digest = store.put_document(b"synthetic factory review packet")
approval = client.get_approval("fixture")
memo = make_memo(approval_id="fixture", approval_binding_digest=approval["binding"]["digest"],
packet=(PacketItem("doc-1", "Factory fixture", digest),))
store.save_memo(memo)
p = store.present(memo.id, principal_sub=session.subject, tenant=session.tenant, principal_type=session.principal_type)
actor = Actor(session.subject, ActorKind.PERSON)
store.acknowledge(p.id, actor, ["h-1"])
return store, p, actor
def test_actual_entry_and_independent_receiver_survive_holder_restart(component, receiver, tmp_path):
client, engine, _, _, _ = component
sink, receiver_transport, backend = receiver
store, p, actor = durable_setup(tmp_path, component)
d = store.record_disposition(p.id, actor, Verb.ACCEPT, operation_id="synthetic-click")
attempt = store.begin_submission(d.id) # Harness only; native policy admission is not exercised.
result = client.add_entry("fixture")
assert store.finish_submission(d.id, attempt, result) == "confirmed"
worker = OutboxWorker(Store(store.path), sink)
assert worker.run_once()["delivered"] == 4
assert worker.reconcile("2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z")["count_values_match"]
recovered = Store(store.path).presentation_for_entry(*result.correlation)
assert recovered[0].id == d.id and recovered[1].id == p.id
assert recovered[3] == {"doc-1": b"synthetic factory review packet"}
assert len(engine.get("fixture").entries) == 1
# This sender can count its stream without gaining archive read privileges.
with pytest.raises(AuditDeliveryError, match="unauthorized"):
sink._request("GET", "/v1/events")
event = store.evidence()[0]
status, body = receiver_transport.request("GET", "https://audit.test/v1/events/" + event["id"],
headers={"Authorization": "Bearer synthetic-auditor-token"})
assert status == 200 and body["details"]["data"]["view_hash"] == p.view_hash
assert "synthetic factory review packet" not in json.dumps(body)
assert not any("synthetic factory review packet" in row["envelope"] for row in store.evidence())
def test_lost_receiver_reply_reuses_exact_event_and_deduplicates(component, receiver, tmp_path):
sink, transport, backend = receiver
store, _, _ = durable_setup(tmp_path, component)
first = store.claim_delivery(now=10)
transport.lose_reply = True
with pytest.raises(AuditDeliveryError, match="unavailable"): sink.deliver(first[0], first[2])
# Simulate crash before marking local delivery; its lease expires after restart.
replay = Store(store.path).claim_delivery(now=41)
assert replay[0] == first[0] and replay[2] == first[2]
reference = sink.deliver(replay[0], replay[2])
store.finish_delivery(replay[0], replay[1], reference=reference)
OutboxWorker(store, sink).run_once()
report = OutboxWorker(store, sink).reconcile("2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z")
assert report["count_values_match"] and report["counts"]["informed-decision.presentation"] == {"source": 2, "receiver": 2}
def test_lost_engine_reply_does_not_rebind_or_reassign_presentation(component, tmp_path):
client, engine, transport, _, _ = component
store, p, actor = durable_setup(tmp_path, component)
d = store.record_disposition(p.id, actor, Verb.ACCEPT, operation_id="synthetic-click")
attempt = store.begin_submission(d.id)
client.add_entry("fixture") # Real API commits; the caller's response is deliberately discarded.
assert store.finish_submission(d.id, attempt) == "unresolved"
calls = len(transport.calls)
with pytest.raises(Conflict): Store(store.path).begin_submission(d.id)
assert len(transport.calls) == calls and len(engine.get("fixture").entries) == 1
assert store.submission(d.id)["approved_at"] is None
def test_delayed_acceptance_has_a_different_window_without_being_lost(storage, receiver, monkeypatch):
from informed_decision import evidence, presentation
monkeypatch.setattr(evidence, "_now", lambda: "2020-06-01T12:00:00Z")
monkeypatch.setattr(presentation, "_now", lambda: "2020-06-01T12:00:00Z")
store, memo = storage
present(store, memo)
sink, _, backend = receiver
worker = OutboxWorker(store, sink)
assert worker.run_once()["delivered"] == 1
historical = worker.reconcile("2020-01-01T00:00:00Z", "2021-01-01T00:00:00Z")
assert not historical["count_values_match"] and not historical["automatic_loss_finding"]
assert worker.reconcile("2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z")["count_values_match"]

318
tests/test_durable_store.py Normal file
View file

@ -0,0 +1,318 @@
from concurrent.futures import ThreadPoolExecutor
from dataclasses import replace
import json
import os
import sqlite3
import subprocess
import sys
import time
import pytest
from informed_decision.approval_client import EntryResult
from informed_decision.disposition import Actor, ActorKind, DispositionRefused, Verb, record
from informed_decision.evidence import EventClass
from informed_decision.memo import Highlight, PacketItem
from informed_decision.provenance import Claim, HumanControlNotDischargeable, Route
from informed_decision.store import Conflict, EvidenceUnavailable, Store, StoreError
from test_skeleton import HUMAN, make_memo
@pytest.fixture
def storage(tmp_path):
private = tmp_path / "private"
private.mkdir(mode=0o700)
store = Store(private / "evidence.sqlite")
digest = store.put_document(b"fixture packet material")
memo = make_memo(packet=(PacketItem("doc-1", "Fixture document", digest),),
brief="private brief sentinel", approval_binding_digest="sha256:" + "a" * 64)
store.save_memo(memo)
return store, memo
def present(store, memo, *, route=Route.AUTHENTICATION):
return store.present(memo.id, principal_sub=HUMAN.sub,
tenant=Claim("tenant:platform", Route.REGISTRATION), principal_type=Claim("human", route))
def prepare(storage, operation_id="click-1"):
store, memo = storage
p = present(store, memo)
store.acknowledge(p.id, HUMAN, ["h-1"])
d = store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id=operation_id)
return p, d
def abort_outbox(store):
with sqlite3.connect(store.path) as db:
db.execute("CREATE TRIGGER injected_failure BEFORE INSERT ON outbox BEGIN SELECT RAISE(ABORT,'injected disk failure'); END")
def test_packet_and_presentation_content_survive_reopen(storage):
store, memo = storage
p = present(store, memo)
reopened = Store(store.path)
saved, original, documents = reopened.retrieve_presentation(p.id)
assert saved == memo and original == p
assert documents == {"doc-1": b"fixture packet material"}
row = reopened.evidence()[0]
assert json.loads(row["content"])["memo"]["brief"] == "private brief sentinel"
assert "private brief sentinel" not in row["envelope"]
assert "fixture packet material" not in row["envelope"]
assert json.loads(row["envelope"])["data"]["content_exists"] is True
assert reopened.outbox()[0]["state"] == "pending"
def test_missing_packet_content_prevents_false_custody_assertion(storage):
store, memo = storage
altered = replace(memo, id="missing", packet=(PacketItem("doc-1", "Missing", "sha256:" + "b" * 64),))
with pytest.raises(EvidenceUnavailable): store.save_memo(altered)
with pytest.raises(EvidenceUnavailable): store.memo("missing")
assert store.evidence() == []
def test_versions_are_immutable_and_old_presentation_cannot_bind(storage):
store, memo = storage
p = present(store, memo)
store.save_memo(memo) # Exact import is harmless.
with pytest.raises(Conflict): store.save_memo(replace(memo, brief="silently changed"))
store.save_memo(memo.next_version(brief="revised"))
with pytest.raises(DispositionRefused, match="G_PRES"):
store.acknowledge(p.id, HUMAN, ["h-1"])
with pytest.raises(DispositionRefused, match="G_PRES"):
store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="stale-click")
saved, _, _ = store.retrieve_presentation(p.id)
assert saved.brief == memo.brief # Retrieval uses the historic version.
@pytest.mark.parametrize("verb", [Verb.ACCEPT, Verb.DECLINE, Verb.ACKNOWLEDGE, Verb.RETURN, Verb.DISCUSS])
def test_actor_cannot_use_someone_elses_presentation(storage, verb):
store, memo = storage
p = present(store, memo)
other = Actor("other-person", ActorKind.PERSON)
with pytest.raises(DispositionRefused, match="G_ACTOR"):
record(memo, p.with_ack("h-1"), verb, other, reasons=("wrong_scope",))
before = store.evidence()
with pytest.raises(DispositionRefused, match="G_ACTOR"):
store.record_disposition(p.id, other, verb, operation_id="stolen-click", reasons=("wrong_scope",))
assert store.evidence() == before
def test_acknowledgments_are_explicit_append_only_and_idempotent(storage):
store, memo = storage
p = present(store, memo)
with pytest.raises(DispositionRefused, match="G_ACK"):
store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="before-ack")
with pytest.raises(DispositionRefused, match="G_ACK"):
store.acknowledge(p.id, HUMAN, ["does-not-exist"])
with pytest.raises(DispositionRefused, match="G_ACTOR"):
store.acknowledge(p.id, Actor("other", ActorKind.PERSON), ["h-1"])
after = store.acknowledge(p.id, HUMAN, ["h-1"])
assert after.view_hash == p.view_hash and after.acked_highlight_ids == {"h-1"}
assert store.acknowledge(p.id, HUMAN, ["h-1"]) == after
assert len(store.evidence()) == 2
with sqlite3.connect(store.path) as db:
original = json.loads(db.execute("SELECT body FROM presentations WHERE id=?", (p.id,)).fetchone()[0])
assert original["acked_highlight_ids"] == []
def test_registration_human_assertion_cannot_bind(storage):
store, memo = storage
p = present(store, memo, route=Route.REGISTRATION)
store.acknowledge(p.id, HUMAN, ["h-1"])
with pytest.raises(HumanControlNotDischargeable):
store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="unverified")
def test_return_discuss_and_decline_remain_distinct_without_engine_jobs(storage):
store, memo = storage
p = present(store, memo)
with pytest.raises(DispositionRefused, match="G_REASONS"):
store.record_disposition(p.id, HUMAN, Verb.RETURN, operation_id="empty-return")
returned = store.record_disposition(p.id, HUMAN, Verb.RETURN, operation_id="return",
reasons=("wrong_scope",), note="private note sentinel")
discussed = store.record_disposition(p.id, HUMAN, Verb.DISCUSS, operation_id="discuss", note="question")
store.acknowledge(p.id, HUMAN, ["h-1"])
declined = store.record_disposition(p.id, HUMAN, Verb.DECLINE, operation_id="decline")
assert [returned.verb, discussed.verb, declined.verb] == [Verb.RETURN, Verb.DISCUSS, Verb.DECLINE]
assert all(store.submission(d.id) is None for d in [returned, discussed, declined])
assert store.retrieve_disposition(returned.id)[0].note == "private note sentinel"
assert all("private note sentinel" not in r["envelope"] for r in store.evidence())
def test_click_idempotency_and_parallel_submission_reservation(storage):
store, memo = storage
p = present(store, memo)
store.acknowledge(p.id, HUMAN, ["h-1"])
def click(_):
return Store(store.path).record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="same-click")
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(click, range(2)))
assert results[0] == results[1]
assert len(store.evidence()) == 3
with pytest.raises(Conflict):
store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="same-click", note="changed")
def reserve(_):
try: return Store(store.path).begin_submission(results[0].id)
except Conflict: return None
with ThreadPoolExecutor(max_workers=2) as pool:
attempts = list(pool.map(reserve, range(2)))
assert sum(a is not None for a in attempts) == 1
@pytest.mark.parametrize("operation", ["present", "acknowledge", "disposition", "completion"])
def test_state_and_outbox_rollback_together(storage, operation):
store, memo = storage
p = present(store, memo)
if operation in ("disposition", "completion"):
store.acknowledge(p.id, HUMAN, ["h-1"])
if operation == "completion":
d = store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="intent")
attempt = store.begin_submission(d.id)
before = store.evidence()
abort_outbox(store)
with pytest.raises(sqlite3.IntegrityError, match="injected"):
if operation == "present": present(store, memo)
elif operation == "acknowledge": store.acknowledge(p.id, HUMAN, ["h-1"])
elif operation == "disposition": store.record_disposition(p.id, HUMAN, Verb.ACCEPT, operation_id="intent")
else: store.finish_submission(d.id, attempt, EntryResult("appr-1", HUMAN.sub, "2026-09-10T20:00:00Z", "approved"))
assert store.evidence() == before
assert len(store.outbox()) == len(before)
if operation == "acknowledge": assert not store.presentation(p.id).acked_highlight_ids
if operation == "completion": assert store.submission(d.id)["state"] == "in_flight"
with sqlite3.connect(store.path) as db:
assert db.execute("SELECT COUNT(*) FROM presentations").fetchone()[0] == 1
assert db.execute("SELECT COUNT(*) FROM dispositions").fetchone()[0] == (1 if operation == "completion" else 0)
def test_process_death_before_commit_loses_neither_half(storage):
store, memo = storage
code = '''import os, sys
from informed_decision.store import Store
from informed_decision.provenance import Claim, Route
s=Store(sys.argv[1]); original=s._event
def die(*args, **kwargs):
original(*args, **kwargs)
os._exit(91)
s._event=die
s.present("memo-1", principal_sub="bernd", tenant=Claim("tenant:platform", Route.REGISTRATION), principal_type=Claim("human", Route.AUTHENTICATION))
'''
result = subprocess.run([sys.executable, "-c", code, str(store.path)])
assert result.returncode == 91
reopened = Store(store.path)
assert reopened.memo(memo.id) == memo and reopened.evidence() == [] and reopened.outbox() == []
with sqlite3.connect(store.path) as db:
assert db.execute("SELECT COUNT(*) FROM presentations").fetchone()[0] == 0
def test_confirmation_points_to_original_ack_snapshot(storage):
store, memo = storage
extra = Highlight("h-2", "doc-1", "optional")
store.save_memo(memo.next_version(highlights=(*memo.highlights, extra)))
p, d = prepare((store, store.memo(memo.id)))
attempt = store.begin_submission(d.id)
result = EntryResult("appr-1", HUMAN.sub, "2026-09-10T20:00:00Z", "approved")
assert store.finish_submission(d.id, attempt, result) == "confirmed"
store.acknowledge(p.id, HUMAN, ["h-2"])
recovered, snapshot, _, _ = Store(store.path).presentation_for_entry(*result.correlation)
assert recovered.id == d.id and snapshot.acked_highlight_ids == {"h-1"}
assert store.presentation(p.id).acked_highlight_ids == {"h-1", "h-2"}
p2 = present(store, store.memo(memo.id))
store.acknowledge(p2.id, HUMAN, ["h-1"])
with pytest.raises(Conflict): store.record_disposition(p2.id, HUMAN, Verb.ACCEPT, operation_id="new-click")
@pytest.mark.parametrize("kind", ["lost-reply", "old-duplicate", "crash-after-reserve"])
def test_uncertain_entry_never_gets_a_new_presentation_or_automatic_retry(storage, kind):
store, memo = storage
p, d = prepare(storage)
attempt = store.begin_submission(d.id)
if kind != "crash-after-reserve":
result = EntryResult("appr-1", HUMAN.sub, "2020-01-01T00:00:00Z", "approved", True) if kind == "old-duplicate" else None
assert store.finish_submission(d.id, attempt, result) == "unresolved"
reopened = Store(store.path)
with pytest.raises(Conflict): reopened.begin_submission(d.id)
with pytest.raises(EvidenceUnavailable): reopened.presentation_for_entry("appr-1", HUMAN.sub, "2020-01-01T00:00:00Z")
assert reopened.submission(d.id)["approved_at"] is None
assert not any(json.loads(r["envelope"])["data"].get("entry_correlation") for r in reopened.evidence())
def test_updated_memo_stops_already_prepared_submission(storage):
store, memo = storage
_, d = prepare(storage)
store.save_memo(memo.next_version(brief="new question"))
with pytest.raises(DispositionRefused, match="G_PRES"): store.begin_submission(d.id)
assert store.submission(d.id)["state"] == "prepared"
def test_revision_cannot_race_an_in_flight_or_uncertain_entry(storage):
store, memo = storage
_, d = prepare(storage)
attempt = store.begin_submission(d.id)
with pytest.raises(Conflict): store.save_memo(memo.next_version(brief="racing edit"))
store.finish_submission(d.id, attempt)
with pytest.raises(Conflict): store.save_memo(memo.next_version(brief="unresolved edit"))
def test_wrong_entry_or_attempt_cannot_confirm(storage):
store, _ = storage
_, d = prepare(storage)
attempt = store.begin_submission(d.id)
with pytest.raises(Conflict): store.finish_submission(d.id, "wrong-attempt")
with pytest.raises(Conflict):
store.finish_submission(d.id, attempt, EntryResult("other-approval", HUMAN.sub, "2026-09-10T20:00:00Z", "approved"))
assert store.submission(d.id)["state"] == "in_flight"
def test_evidence_is_append_only_and_backup_preserves_pending_and_confirmed(storage):
store, _ = storage
p, d = prepare(storage)
attempt = store.begin_submission(d.id)
result = EntryResult("appr-1", HUMAN.sub, "2026-09-10T20:00:00Z", "approved")
store.finish_submission(d.id, attempt, result)
with sqlite3.connect(store.path) as db:
for statement in ["DELETE FROM evidence", "UPDATE presentations SET body='{}'", "DELETE FROM documents", "DELETE FROM dispositions"]:
with pytest.raises(sqlite3.IntegrityError, match="immutable"): db.execute(statement)
destination = store.path.parent / "backup.sqlite"
store.backup(destination)
backup = Store(destination)
assert backup.outbox() == store.outbox()
assert backup.presentation_for_entry(*result.correlation)[0].id == d.id
with pytest.raises(Conflict): store.backup(destination)
def test_store_refuses_unsafe_paths_and_unknown_schema(tmp_path):
private = tmp_path / "private"
private.mkdir(mode=0o755)
with pytest.raises(StoreError): Store(private / "db")
private.chmod(0o700)
db = private / "db"
db.touch(mode=0o644)
with pytest.raises(StoreError): Store(db)
db.chmod(0o600)
with sqlite3.connect(db) as conn: conn.execute("PRAGMA user_version=99")
with pytest.raises(StoreError, match="schema"): Store(db)
link = private / "linked"
link.symlink_to(db)
with pytest.raises(StoreError): Store(link)
def test_expired_delivery_lease_replays_same_id_and_bytes(storage):
store, memo = storage
present(store, memo)
first = store.claim_delivery(now=10)
assert store.claim_delivery(now=11) is None
again = Store(store.path).claim_delivery(now=41)
assert first[0] == again[0] and first[2] == again[2] and first[1] != again[1]
with pytest.raises(Conflict): store.finish_delivery(first[0], first[1], reference="old-receipt")
def test_heartbeats_do_not_mask_backlog_and_are_per_class(storage):
store, memo = storage
present(store, memo)
future = time.time() + 90000
assert store.queue_heartbeats(now=future) == 2
assert store.queue_heartbeats(now=future) == 0
beats = [json.loads(r["envelope"]) for r in store.evidence() if r["class"] == EventClass.HEARTBEAT.value]
assert {b["data"]["class"] for b in beats} == {EventClass.DISPOSITION.value, EventClass.STANCE_APPLICATION.value}
assert all(b["data"]["assertion"] == "nothing-to-report" for b in beats)

View file

@ -181,7 +181,7 @@ def test_agent_cannot_perform_a_binding_verb(verb):
def test_agent_may_still_comment():
memo = make_memo()
pres = render(memo, principal_sub="bot")
pres = render(memo, principal_sub=AGENT.sub)
assert record(memo, pres, Verb.COMMENT, AGENT).verb is Verb.COMMENT
@ -434,7 +434,7 @@ def test_drain_failure_leaves_the_record_pending_never_lost():
def test_drain_delivers_and_counts_reconcile_per_class():
outbox = Outbox()
memo = make_memo()
pres = render(memo, principal_sub="b").with_ack("h-1")
pres = render(memo, principal_sub=HUMAN.sub).with_ack("h-1")
outbox.append(commit_presentation(pres, custody=CUSTODY))
outbox.append(commit_disposition(record(memo, pres, Verb.ACCEPT, HUMAN), custody=CUSTODY))
sent: list[dict] = []