Files
multi-agent-mux/.agents/skills/lib_py/loop/attribution.py
T
GodopuandClaude Opus 5 2532e78453 feat(issue-5): single CAS transition boundary for every job-status write
engine.transition is now the only writer of job status. It implements the target
table in state-transitions.md §3: claim, non-terminal metadata, explicit
terminals, idempotent re-receipt, refusal of conflicting or late signals with an
audit entry, stale-attempt rejection, and deadline timeout.

Two write paths become one. update_job_status merged fields unconditionally;
pick_pending hand-rolled a correct pending->running swap plus its own audit
mirror. Neither was wrong alone, but a rule added to one silently missed the
other, which is the shape that produced the phase 1 transport bugs. Both keep
their signatures and their preserved legacy bodies behind MAM_ENGINE_CAS=0.

A merged `status` value is routed to the request that owns it, so a raw field
write can no longer slip past the table. Getting that wrong first was caught by
the characterization tests: a metadata merge was overwriting the caller's status
with the pre-merge value.

Lock discipline is now enforced, not described. The lock covers a decision and
one record write; audit mirroring is queued and flushed after release, carrying a
monotonic transition_seq so ordering survives the move. A test watches for
subprocess, socket and sleep calls while the lock is held and asserts none.

Structural correction to my own plan, caught by a phase 1 guard: the plan put the
engine in lib_py/loop, but delegation would then have had to import a sibling,
which is exactly what mixes trees under a frozen loop. The engine belongs with
the registry writers, so it lives in lib_py/delegation and loop consumes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
2026-09-06 18:53:22 +09:00

60 lines
2.4 KiB
Python

"""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 '<pane_id>:<pid>:<start>'.
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) + ":")