"""Four-fold execution attribution, as a pure predicate. A terminal state may only be inferred for the attempt that actually ran. Idle is not evidence of finishing: a reused session sitting idle from previous work looks identical unless job, attempt, session and pane generation all agree AND the attempt was observed to actually execute after dispatch was acknowledged. """ from __future__ import annotations REQUIRED_FIELDS = ("job_id", "attempt_id", "agent_session_uuid", "pane_process_generation") class AttributionResult(object): __slots__ = ("ok", "reason") def __init__(self, ok, reason): self.ok = ok self.reason = reason def check(dispatch_context, observed=None) -> AttributionResult: """Compare a dispatch record against what was observed. `observed` may carry any subset of the four identifiers; every key it does supply must match. A missing identifier is never treated as agreement. """ if not isinstance(dispatch_context, dict): return AttributionResult(False, "dispatch_context missing") for field in REQUIRED_FIELDS: if field not in dispatch_context: return AttributionResult(False, "missing attribution field: {}".format(field)) value = dispatch_context[field] if value is None or value == "": return AttributionResult(False, "empty attribution field: {}".format(field)) if not dispatch_context.get("dispatch_ack"): return AttributionResult(False, "dispatch was never acknowledged") # The anchor is the whole point: without it, idle is just idle. if not dispatch_context.get("execution_anchor_observed"): return AttributionResult(False, "no execution anchor observed for this attempt") for field, value in (observed or {}).items(): if field in REQUIRED_FIELDS and value != dispatch_context[field]: return AttributionResult( False, "attribution mismatch on {}".format(field)) return AttributionResult(True, "four-fold attribution satisfied") def pane_matches_generation(pane_id, pane_process_generation) -> bool: """`pane_process_generation` is '::'. Compared on the whole leading segment so w2X:p1 never matches w2X:p10. """ if not pane_id or not pane_process_generation: return False return str(pane_process_generation).startswith(str(pane_id) + ":")