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:
tegwick 2026-10-04 20:17:24 +02:00
parent ee472acc87
commit 5fec7de4cb
21 changed files with 1022 additions and 78 deletions

View file

@ -20,7 +20,7 @@ import uuid
from .approval_client import EntryResult
from .disposition import Actor, BINDING_VERBS, DispositionRefused, Verb, record
from .evidence import EventClass, commit_disposition, commit_presentation, commit_stance_application, heartbeat, HEARTBEAT_CLASSES
from .evidence import Commitment, EventClass, commit_disposition, commit_presentation, commit_stance_application, heartbeat, HEARTBEAT_CLASSES
from .memo import Memo
from .presentation import render
from .provenance import Claim, Route, assert_human_control_dischargeable
@ -556,18 +556,50 @@ class Store:
"(SELECT id FROM outbox WHERE state='delivered')")
return result.rowcount
def stage_lifecycle_audit(self):
"""Opt-in commitment-only delivery through the existing audit outbox."""
with self._transaction() as db:
rows = db.execute("SELECT l.* FROM lifecycle_pending p JOIN lifecycle_events l ON l.id=p.event_id "
"ORDER BY l.sequence LIMIT 100").fetchall()
staged = 0
for row in rows:
if row["tenant"] != "tenant:platform":
raise Conflict("lifecycle audit tenant not admitted by this runtime")
content = {"event": dict(row), "memo": self._memo(db, row["memo_id"], row["version"])}
existing = db.execute("SELECT class,content FROM evidence WHERE id=?", (row["id"],)).fetchone()
if existing:
if existing["class"] != EventClass.LIFECYCLE.value or existing["content"] != dumps(content):
raise Conflict("lifecycle evidence id collision")
continue
commitment = Commitment(row["id"], EventClass.LIFECYCLE, row["at"], {
"memo_id": row["memo_id"], "memo_version": row["version"], "operation": row["kind"],
"content_sha256": "sha256:" + hashlib.sha256(dumps(content).encode()).hexdigest(),
"content_exists": True, "custody": "informed-decision:lifecycle:" + row["id"]})
self._event(db, commitment, content)
staged += 1
return staged
def settle_lifecycle_audit(self):
with self._transaction() as db:
return db.execute("DELETE FROM lifecycle_pending WHERE event_id IN "
"(SELECT o.id FROM outbox o JOIN evidence e ON e.id=o.id "
"WHERE o.state='delivered' AND o.receiver_reference IS NOT NULL AND e.class=?)",
(EventClass.LIFECYCLE.value,)).rowcount
def intake_audit_pending(self):
with self._connection() as db:
return db.execute("SELECT 1 FROM intake_deliveries WHERE kind='memo-posed' "
"AND state!='delivered' LIMIT 1").fetchone() is not None
def queue_heartbeats(self, now=None, *, include_posed=False):
def queue_heartbeats(self, now=None, *, include_posed=False, include_lifecycle=False):
now = time.time() if now is None else now
count = 0
with self._transaction() as db:
classes = {**HEARTBEAT_CLASSES}
if include_posed:
classes[EventClass.MEMO_POSED.value] = 86400
if include_lifecycle:
classes[EventClass.LIFECYCLE.value] = 86400
for event_class, interval in classes.items():
if db.execute("SELECT 1 FROM outbox o JOIN evidence e ON e.id=o.id WHERE o.state!='delivered' "
"AND (e.class=? OR (e.class=? AND json_extract(e.envelope,'$.data.class')=?)) LIMIT 1",