Implement lifecycle workspace publication and audit delivery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
ee472acc87
commit
5fec7de4cb
21 changed files with 1022 additions and 78 deletions
80
tests/test_lifecycle_audit.py
Normal file
80
tests/test_lifecycle_audit.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
from dataclasses import replace
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from informed_decision.audit import OutboxWorker
|
||||
from informed_decision.evidence import EventClass
|
||||
from informed_decision.lifecycle import LifecycleRepository
|
||||
from informed_decision.runtime import AuditPump
|
||||
from informed_decision.store import Conflict, Store
|
||||
from test_durable_component import receiver
|
||||
from test_lifecycle import AGENT, workspace
|
||||
|
||||
|
||||
def register_platform(workspace):
|
||||
repo, memo = workspace
|
||||
actor = replace(AGENT, tenant="tenant:platform")
|
||||
event_id = repo.register(actor, memo, key="register")
|
||||
return repo.store, memo, event_id
|
||||
|
||||
|
||||
def test_lifecycle_commitment_is_content_free_stable_and_verifiable(workspace):
|
||||
store, memo, event_id = register_platform(workspace)
|
||||
assert store.stage_lifecycle_audit() == 1
|
||||
assert store.stage_lifecycle_audit() == 0
|
||||
evidence = store.evidence()[0]
|
||||
envelope = json.loads(evidence["envelope"])
|
||||
assert envelope["id"] == event_id and envelope["type"] == EventClass.LIFECYCLE.value
|
||||
assert memo.brief not in evidence["envelope"] and memo.question not in evidence["envelope"]
|
||||
assert envelope["data"]["content_sha256"] == "sha256:" + hashlib.sha256(evidence["content"].encode()).hexdigest()
|
||||
assert store.settle_lifecycle_audit() == 0
|
||||
assert store.lifecycle_counts()["pending"] == 1
|
||||
assert Store(store.path).evidence() == store.evidence()
|
||||
|
||||
|
||||
def test_lifecycle_delivery_uses_actual_receiver_and_recovers_lost_reply(workspace, receiver):
|
||||
store, _, event_id = register_platform(workspace)
|
||||
sink, transport, _ = receiver
|
||||
store.stage_lifecycle_audit()
|
||||
transport.lose_reply = True
|
||||
worker = OutboxWorker(store, sink)
|
||||
assert worker.run_once()["retrying"] == 1
|
||||
assert store.settle_lifecycle_audit() == 0
|
||||
with store._connection() as db:
|
||||
db.execute("UPDATE outbox SET next_attempt=0 WHERE id=?", (event_id,))
|
||||
reopened = Store(store.path)
|
||||
assert OutboxWorker(reopened, sink).run_once()["delivered"] == 1
|
||||
assert reopened.settle_lifecycle_audit() == 1
|
||||
assert reopened.lifecycle_counts() == {"records": 1, "events": 1, "pending": 0}
|
||||
assert worker.reconcile("2020-01-01T00:00:00Z", "2100-01-01T00:00:00Z")["counts"][EventClass.LIFECYCLE.value] == {"source": 1, "receiver": 1}
|
||||
|
||||
|
||||
def test_runtime_delivery_is_explicit_and_restores_readiness_only_after_receipts(workspace, receiver):
|
||||
store, _, _ = register_platform(workspace)
|
||||
sink, _, _ = receiver
|
||||
disabled = AuditPump(store, sink)
|
||||
disabled.tick()
|
||||
assert not disabled.ready() and store.lifecycle_counts()["pending"] == 1
|
||||
enabled = AuditPump(store, sink, lifecycle_delivery=True)
|
||||
enabled.tick()
|
||||
assert enabled.ready() and store.lifecycle_counts()["pending"] == 0
|
||||
assert store.evidence()[0]["class"] != EventClass.LIFECYCLE.value # Legacy heartbeats predate opt-in.
|
||||
|
||||
|
||||
def test_unadmitted_tenant_remains_pending_without_audit_relabelling(workspace):
|
||||
repo, memo = workspace
|
||||
repo.register(AGENT, memo, key="register")
|
||||
with pytest.raises(Conflict, match="tenant not admitted"):
|
||||
repo.store.stage_lifecycle_audit()
|
||||
assert repo.store.evidence() == []
|
||||
assert repo.store.lifecycle_counts()["pending"] == 1
|
||||
|
||||
|
||||
def test_pending_events_do_not_get_a_nothing_to_report_heartbeat(workspace):
|
||||
store, _, _ = register_platform(workspace)
|
||||
store.stage_lifecycle_audit()
|
||||
store.queue_heartbeats(include_lifecycle=True)
|
||||
assert not any(json.loads(row["envelope"])["data"].get("class") == EventClass.LIFECYCLE.value
|
||||
for row in store.evidence() if row["class"] == EventClass.HEARTBEAT.value)
|
||||
Loading…
Add table
Add a link
Reference in a new issue