"""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 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): self.store, self.worker, self.clock = store, OutboxWorker(store, sink), clock 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: return self._last_ok is not None and self.clock() - self._last_ok < 90 def tick(self): try: self.store.queue_heartbeats() result = self.worker.run_once(limit=10) 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()) 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 @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) != {"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") 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) store = Store(data["evidence_db"]) controller = ReviewController(store, client, lambda session: ApprovalHTTPClient( approval_origin, session, allow_internal_http=True)) return cls(controller, AuditPump(store, sink))