"""Owner-configured runtime and scheduled durable audit delivery. This loads explicit configuration; it neither provisions credentials nor admits a deployment. Projected caller tokens and Audit Core sender custody have owners. """ from dataclasses import dataclass from datetime import datetime, timezone import json import os from pathlib import Path import stat import tempfile import threading import time from .approval_http import ApprovalHTTPClient, ordinary_review_ids from .audit import AuditCoreSink, OutboxWorker from .policy import PolicyClient from .review import ReviewController from .store import Store def token_file(path): path = Path(path) if not path.is_absolute(): raise ValueError("credential file path must be absolute") def read(): with path.open("rb") as handle: value = handle.read(32769) if len(value) > 32768: raise ValueError("credential file too large") return value.decode("ascii").strip() return read class AuditPump: """One process, bounded batches, 30s ticks; never drops a failed record.""" 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() self._last_ok = None self._last_reconciled = 0 def ready(self): with self._lock: 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: if self.intake_worker: self.intake_worker.run_once(limit=10) 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") report = self.worker.reconcile(stamp(now - 86400), stamp(now)) # Report the two time bases, not an invented loss/completeness # result. A retained private snapshot is for operator inspection. directory = self.store.path.parent fd, temporary = tempfile.mkstemp(prefix=".reconciliation-", dir=directory) try: with os.fdopen(fd, "w") as handle: json.dump(report, handle, sort_keys=True) handle.write("\n"); handle.flush(); os.fsync(handle.fileno()) os.replace(temporary, directory / "audit-reconciliation.json") finally: if os.path.exists(temporary): os.unlink(temporary) self._last_reconciled = now healthy = not result["retrying"] and not result["blocked"] and not any( 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: # Keep readiness closed and retry next tick. Never log response # bodies, file paths, bearer tokens or a fabricated human decline. with self._lock: self._last_ok = None def start(self): if self._thread is not None: raise RuntimeError("audit delivery already started") def run(): while not self._stop.is_set(): self.tick() self._stop.wait(30) self._thread = threading.Thread(target=run, name="infd-audit-delivery", daemon=True) self._thread.start() def stop(self): self._stop.set() if self._thread is not None: self._thread.join(timeout=6) @dataclass(frozen=True) class Runtime: controller: ReviewController pump: AuditPump lifecycle: object | None = None @classmethod def from_file(cls, filename): path = Path(filename) info = path.lstat() if (not path.is_absolute() or not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) != 0o600 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", "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") from .http_transport import fixed_origin approval_origin = fixed_origin(data["approval_origin"], allow_internal_http=True) policy = data["policy"] client = PolicyClient(policy["origin"], token_file(policy["caller_token_file"]), package=policy["package"], version=policy["version"], package_digest=policy["package_digest"], allow_internal_http=True) sink = AuditCoreSink(data["audit"]["origin"], token_file(data["audit"]["sender_token_file"]), allow_internal_http=True) delivery = data.get('intake_delivery') hub = None if 'intake_delivery' in data: from .intake_hub import HubDelivery 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))) worker = None if hub: from .intake_delivery import IntakeDeliveryWorker worker = IntakeDeliveryWorker(store, audit=sink, notify=hub.notify, hub_transport=hub.resolution, hub_base_url=hub.origin) 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)