"""The completion decision. A pure function, deliberately. No I/O, no clock, no globals: that is what lets the phase-0 scenario fixtures be fed to it verbatim as test vectors. Those seven scenarios and their `expected_policy.decision` survived three-reviewer scrutiny, so they are the contract this implements rather than a fresh set of rules. Fail-closed throughout: every gate must pass for `candidate_approved`. Anything unproven holds. """ from __future__ import annotations from lib_py.loop import attribution from lib_py.loop.evidence import Decision # Native states that mean "not doing anything right now". Idle alone proves # nothing about completion; it only makes completion *possible* to consider. QUIESCENT = ("idle", "done") def _timeline_by_kind(scenario): out = {} for item in scenario.get("timeline") or []: out.setdefault(item.get("source_kind"), []).append(item) return out def collect_observed_identity(scenario) -> dict: """Gather identifiers the *evidence* carries, for comparison with dispatch. Without this, attribution only proves the dispatch record is internally well-formed -- it never confronts what was actually seen. Association of a job or attempt to an observation is the external collector's business, so these are read from the timeline items and the report manifest, never invented as native Herdr fields. """ observed = {} for item in scenario.get("timeline") or []: payload = item.get("payload") or {} for field in ("job_id", "attempt_id", "agent_session_uuid", "pane_process_generation"): if payload.get(field) is not None: # Every occurrence is kept. Taking only the first would let a # later, contradicting observation pass unnoticed -- which is the # opposite of what this collection is for. observed.setdefault(field, []).append(payload[field]) manifest = payload.get("report_manifest") if isinstance(manifest, dict) and manifest.get("attempt_id") is not None: observed.setdefault("manifest_attempt_id", []).append(manifest["attempt_id"]) return observed def decide(scenario) -> Decision: """Map one composite scenario to a decision. Order matters: transport doubt and staleness invalidate the evidence itself, so they are answered before anything is inferred from it. """ dispatch = scenario.get("dispatch_context") or {} by_kind = _timeline_by_kind(scenario) herdr_items = by_kind.get("herdr") or [] latest = herdr_items[-1]["payload"] if herdr_items else {} filesystem = scenario.get("filesystem") or {} # 1. Transport doubt: evidence gathered before a gap cannot be trusted. for item in herdr_items: payload = item.get("payload") or {} if payload.get("transport_healthy") is False or payload.get("error"): return Decision("hold_reconnect", "herdr transport failed; cached observations are void") # 2. A signal from a previous attempt or process generation decides nothing. for item in herdr_items: payload = item.get("payload") or {} pane_generation = payload.get("pane_process_generation") if pane_generation and dispatch.get("pane_process_generation") and \ pane_generation != dispatch["pane_process_generation"]: return Decision("reject_stale", "signal belongs to a different process generation") pane_id = payload.get("pane_id") if pane_id and dispatch.get("pane_process_generation") and \ not attribution.pane_matches_generation( pane_id, dispatch["pane_process_generation"]): return Decision("reject_stale", "signal belongs to a different pane") # 3. A blocked agent is waiting for a human. Completion is forbidden. if latest.get("agent_status") == "blocked" or latest.get("matched_rule"): return Decision("hold_blocked", "agent is blocked on an interactive prompt") # 4. Attribution: the dispatch record must be well formed AND agree with the # identifiers the evidence itself carries. Flags on the dispatch record # alone are self-assertion, not correspondence. observed = collect_observed_identity(scenario) manifest_attempts = observed.pop("manifest_attempt_id", []) attributed = attribution.check(dispatch) if not attributed.ok: return Decision("hold", attributed.reason) for field, values in observed.items(): for value in values: if value != dispatch.get(field): return Decision("hold", "attribution mismatch on {}: observed {!r}, " "dispatched {!r}".format(field, value, dispatch.get(field))) # The finalized report must belong to the attempt being judged; a manifest # from another attempt is evidence of a different run finishing. for manifest_attempt in manifest_attempts: if manifest_attempt != dispatch.get("attempt_id"): return Decision("hold", "report manifest belongs to attempt {!r}, not {!r}".format( manifest_attempt, dispatch.get("attempt_id"))) # 5. Still working, or a tool is running underneath a ready prompt. if latest.get("agent_status") not in QUIESCENT: return Decision("hold", "agent is not quiescent (status={!r})".format( latest.get("agent_status")), attribution_ok=True) for item in by_kind.get("harness") or []: if (item.get("payload") or {}).get("active"): return Decision("hold", "a tool is still executing", attribution_ok=True) # 6. The report must be finalized, not merely paused. A debounce alone is a # candidate filter, never an authority to complete. if not filesystem.get("exists"): return Decision("hold", "no report present", attribution_ok=True) if not filesystem.get("manifest_committed"): return Decision("hold", "report is not finalized (no commit manifest)", attribution_ok=True) if not filesystem.get("hash_verified"): return Decision("hold", "report manifest hash unverified", attribution_ok=True) return Decision("candidate_approved", "attribution, quiescence and a verified manifest all hold", attribution_ok=True)