Align lifecycle persistence with review isolation and runtime health

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
tegwick 2026-10-04 18:34:40 +02:00
parent b2a4b863a8
commit f0e9341650
8 changed files with 102 additions and 5 deletions

View file

@ -123,5 +123,6 @@ def admin():
else:
with store._connection() as db:
print(json.dumps({'schema_version': db.execute('PRAGMA user_version').fetchone()[0],
'lifecycle': store.lifecycle_counts(),
'outbox': {r[0]: r[1] for r in db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')},
'submissions': {r[0]: r[1] for r in db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')}}))

View file

@ -47,7 +47,11 @@ class AuditPump:
def ready(self):
with self._lock:
return self._last_ok is not None and self.clock() - self._last_ok < 90
recent = self._last_ok is not None and self.clock() - self._last_ok < 90
try:
return recent and self.store.lifecycle_counts()["pending"] == 0
except Exception:
return False
def tick(self):
try:
@ -78,6 +82,8 @@ class AuditPump:
row["state"] != "delivered" for row in self.store.outbox())
if self.intake_worker and self.store.intake_audit_pending():
healthy = False
if self.store.lifecycle_counts()["pending"]:
healthy = False
with self._lock:
self._last_ok = now if healthy else None
except Exception:

View file

@ -168,6 +168,8 @@ class Store:
def save_memo(self, memo: Memo):
with self._transaction() as db:
if db.execute("SELECT 1 FROM lifecycle_records WHERE memo_id=?", (memo.id,)).fetchone():
raise Conflict("registered lifecycle memos require lifecycle revision commands")
self._save_memo(db, memo)
def _save_memo(self, db, memo: Memo):
@ -421,13 +423,23 @@ class Store:
"SELECT body FROM dispositions WHERE presentation_id=? ORDER BY rowid", (presentation_id,))]
def memos_for(self, subject):
"""Latest version of every held memo naming ``subject`` as the person bound."""
"""Legacy review candidates; unpublished lifecycle records stay private."""
with self._connection() as db:
rows = db.execute("SELECT m.body FROM memos m WHERE m.version="
"(SELECT MAX(version) FROM memos WHERE id=m.id) ORDER BY m.id").fetchall()
"(SELECT MAX(version) FROM memos WHERE id=m.id) "
"AND NOT EXISTS (SELECT 1 FROM lifecycle_records l WHERE l.memo_id=m.id) "
"ORDER BY m.id").fetchall()
memos = (memo_from(json.loads(r[0])) for r in rows)
return [m for m in memos if m.binding.principal.id == subject and m.binding.principal.kind == "person"]
def lifecycle_counts(self):
"""Content-free operational counts, including undelivered lifecycle evidence."""
with self._connection() as db:
return {name: db.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
for name, table in (("records", "lifecycle_records"),
("events", "lifecycle_events"),
("pending", "lifecycle_pending"))}
def dispositions_by(self, subject, memo_id):
"""``subject``'s dispositions on every version of a memo, oldest first, with any submission."""
with self._connection() as db: