"""The one data shape that crosses the watcher -> engine boundary. Keeping this a plain value with no I/O is what lets `policy.decide` stay a pure function, which in turn is what lets the phase-0 scenario fixtures be fed to it verbatim as test vectors. """ from __future__ import annotations SOURCE_KINDS = ("herdr", "event", "filesystem", "harness") # Decisions the policy may return. Same vocabulary as the phase-0 contracts, so # a scenario's expected_policy.decision can be compared directly. DECISIONS = ("hold", "hold_blocked", "hold_reconnect", "reject_stale", "candidate_approved") class Observation(object): """One point-in-time fact from one source. Never a conclusion.""" __slots__ = ("source_kind", "payload", "captured_at", "capture_ref", "provenance") def __init__(self, source_kind, payload, captured_at=None, capture_ref=None, provenance="observed"): if source_kind not in SOURCE_KINDS: raise ValueError("unsupported source_kind: {!r}".format(source_kind)) self.source_kind = source_kind self.payload = dict(payload or {}) self.captured_at = captured_at self.capture_ref = capture_ref self.provenance = provenance def __repr__(self): # pragma: no cover - debugging aid return "Observation({!r}, {!r})".format(self.source_kind, self.payload) class Decision(object): """What the policy concluded, and why. `reason` is for the audit trail.""" __slots__ = ("decision", "reason", "attribution_ok") def __init__(self, decision, reason, attribution_ok=False): if decision not in DECISIONS: raise ValueError("unknown decision: {!r}".format(decision)) self.decision = decision self.reason = reason self.attribution_ok = attribution_ok @property def would_complete(self) -> bool: return self.decision == "candidate_approved" def as_dict(self) -> dict: return {"decision": self.decision, "reason": self.reason, "attribution_ok": self.attribution_ok} def __repr__(self): # pragma: no cover - debugging aid return "Decision({!r}, {!r})".format(self.decision, self.reason)