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

@ -36,9 +36,10 @@ def token_file(path):
class AuditPump:
"""One process, bounded batches, 30s ticks; never drops a failed record."""
def __init__(self, store, sink, *, clock=time.time, intake_worker=None):
def __init__(self, store, sink, *, clock=time.time, intake_worker=None, lifecycle_delivery=False):
self.store, self.worker, self.clock = store, OutboxWorker(store, sink), clock
self.intake_worker = intake_worker
self.lifecycle_delivery = lifecycle_delivery
self._stop = threading.Event()
self._thread = None
self._lock = threading.Lock()
@ -57,10 +58,15 @@ class AuditPump:
try:
if self.intake_worker:
self.intake_worker.run_once(limit=10)
self.store.queue_heartbeats(include_posed=self.intake_worker is not None)
if self.lifecycle_delivery:
self.store.stage_lifecycle_audit()
self.store.queue_heartbeats(include_posed=self.intake_worker is not None,
include_lifecycle=self.lifecycle_delivery)
result = self.worker.run_once(limit=10)
if self.intake_worker:
self.store.settle_intake_audit()
if self.lifecycle_delivery:
self.store.settle_lifecycle_audit()
now = self.clock()
if now - self._last_reconciled >= 300:
stamp = lambda t: datetime.fromtimestamp(t, timezone.utc).isoformat(timespec="microseconds")
@ -112,6 +118,7 @@ class AuditPump:
class Runtime:
controller: ReviewController
pump: AuditPump
lifecycle: object | None = None
@classmethod
def from_file(cls, filename):
@ -122,11 +129,14 @@ class Runtime:
or info.st_size > 16384):
raise ValueError("runtime configuration must be an owned private 0600 file")
data = json.loads(path.read_text())
if ((set(data) - {"ordinary_approval_ids", "intake_delivery"}) != {"schema", "evidence_db", "policy", "approval_origin", "audit"}
if ((set(data) - {"ordinary_approval_ids", "intake_delivery", "lifecycle_policy", "lifecycle_delivery"}) != {"schema", "evidence_db", "policy", "approval_origin", "audit"}
or data["schema"] != "informed-decision.review-runtime.v1"
or set(data["policy"]) != {"origin", "package", "version", "package_digest", "caller_token_file"}
or set(data["audit"]) != {"origin", "sender_token_file"}):
raise ValueError("invalid review runtime configuration")
lifecycle_delivery = data.get("lifecycle_delivery", False)
if type(lifecycle_delivery) is not bool:
raise ValueError("invalid lifecycle delivery setting")
ordinary = ordinary_review_ids(data.get("ordinary_approval_ids", []))
if not Path(data["evidence_db"]).is_absolute():
raise ValueError("absolute evidence database path required")
@ -145,6 +155,17 @@ class Runtime:
if not isinstance(delivery, dict) or set(delivery) != {'hub_origin'}:
raise ValueError('invalid intake delivery configuration')
hub = HubDelivery(delivery['hub_origin'])
lifecycle_policy = None
if "lifecycle_policy" in data:
configuration = data["lifecycle_policy"]
if (not isinstance(configuration, dict)
or set(configuration) != {"origin", "package", "version", "package_digest", "caller_token_file"}):
raise ValueError("invalid lifecycle policy configuration")
if configuration["package"] == policy["package"]:
raise ValueError("lifecycle policy must have a separate admission package")
lifecycle_policy = PolicyClient(configuration["origin"], token_file(configuration["caller_token_file"]),
package=configuration["package"], version=configuration["version"],
package_digest=configuration["package_digest"], allow_internal_http=True)
store = Store(data["evidence_db"])
controller = ReviewController(store, client, lambda session: ApprovalHTTPClient(
approval_origin, session, allow_internal_http=True, ordinary_approval_ids=tuple(ordinary)))
@ -153,4 +174,10 @@ class Runtime:
from .intake_delivery import IntakeDeliveryWorker
worker = IntakeDeliveryWorker(store, audit=sink, notify=hub.notify,
hub_transport=hub.resolution, hub_base_url=hub.origin)
return cls(controller, AuditPump(store, sink, intake_worker=worker))
lifecycle = None
if lifecycle_policy is not None:
from .lifecycle import LifecycleRepository
from .lifecycle_service import LifecycleService
lifecycle = LifecycleService(LifecycleRepository(store), lifecycle_policy)
return cls(controller, AuditPump(store, sink, intake_worker=worker,
lifecycle_delivery=lifecycle_delivery), lifecycle)