Repository navigation
Dirty Runs: implement fenced A → B claims and controlled lease takeover #703
Description
Parent epic: #699. Depends on #702 and its reviewed contract #700.
Deliverable
Internal/versioned start, claim, and bounded controlled recovery functions for real A → B runtime tasks. Return the winning task and its claim_id; never treat message reservation or worker identity as claim ownership.
Start and admission
Validate the entire #700 envelope before runtime/message mutation. Create run, both step states, stable work row, A's task/message, and definition/protocol identity atomically. Queue A before start returns; B stays undispatched. Root A's existing input semantics and effective timeout apply. Work can be clean while A executes.
Match stored queue identity, mode, and exact step selector. Preserve guarded UPDATE ... RETURNING winners and actual PGMQ visibility checks from v1. Each proof claim/recovery call targets one explicit task/run and at most one message ID; reject larger or duplicate-filled arrays before expansion, scans, or mutation. There is no cross-run claim batch or recovery sweep in this slice. Use the shared lock order; only winning task rows reach handlers. Persist the owned physical message pair, lease/recovery horizon, and effective policy in history.
New claims require a started run, eligible started step/queued task, compatible identity, and clean engine health/caught-up generations. Dirty/retry-wait/attention tasks cannot newly execute. Reject stale/wrong-message/wrong-route inputs without archive or visibility writes from the claim function. PGMQ reservation itself remains a separate earlier read.
Controlled lease recovery/takeover
Use the protocol decision from #700 for horizon expiry versus locked revocation. Candidate selection is not authority. Under run → work → ordered step/task → queue locks, recheck eligibility/horizon/current token, revoke the old claim, count one infrastructure loss, and requeue or create the permitted replacement claim atomically. Never change handler-failure accounting to manufacture exhaustion.
Preserve the old history and apply visibility only to the task's actual stored pair. Bounded history/one controlled takeover is an internal proof limit, not #689's automatic worker-loss policy. On reaching that limit, durable engine health blocks further claims without archiving proof messages or pretending business exhaustion succeeded.
Do not add a cron recovery driver or strict worker-level ownership (#697). Multiple competing workers are allowed; only one current task claim wins.
Acceptance
- Start commits A's actual message and stable work; B has no task/message. Unsupported shape/options/bytes fail atomically before state exists.
- Separate sessions claiming one delivery produce one token/history row and one empty/rejected result; actual VT matches snapshotted timeout/recovery semantics. Singleton-limit violations, including duplicate-filled ID arrays, fail before task/history/visibility/health changes.
- Flow mode and private-step selector behavior match the stored route; identical numeric message IDs in different queues cannot cross-claim. IDs beyond JavaScript's safe integer range remain exact.
- Dirty/retry-wait/attention/terminal/definition-mismatched state blocks new claims without protocol queue effects.
- Takeover before its horizon cannot revoke; valid takeover records one infrastructure loss and new ownership without rewriting old evidence or handler failures.
- Hand off deterministic settlement/takeover schedules to Dirty Runs: persist replayable settlements without downstream orchestration #704: old settlement wins and prevents takeover, or takeover wins and old settlement is stale. This issue closes on its standalone start/claim/takeover checks and that explicit handoff. Dirty Runs: persist replayable settlements without downstream orchestration #704 owns the mandatory real settlement race; the combined ownership protocol remains unproven until Dirty Runs: persist replayable settlements without downstream orchestration #704 and the Dirty Runs: connect the typed worker path and close the A → B evidence gate #706 evidence gate pass.
Add focused pgTAP and deterministic separate-session barriers. No random sleep or sequential simulation counts as proof of the race.
Activity