Repository navigation
Dirty Runs: persist replayable settlements without downstream orchestration #704
Description
Parent epic: #699. Depends on #703; contract: #700.
Deliverable
Internal/versioned success and exhausted-failure settlement for the bounded real-schema slice, with a typed durable result. Use claim_id plus stored claim/task identity; do not settle by logical task address alone.
Settlement order and replay
Lock run → work → ordered step states → ordered tasks → ordered queue/message pairs. Resolve claim history and verify any supplied task identity. Identical already-settled kind/canonical payload returns its stored disposition without effects, even after newer ownership or terminal run. Different payload/kind returns conflict. Unknown/revoked or current-but-ineligible first submissions return stale/terminal.
For first acceptance, check protocol/definition and task/step/run eligibility before validation, persistence, archive, or VT writes. An expired lease obeys the reviewed pre-revocation policy; it is not independently proof of a completed remote effect.
Success transaction
Store the task result and claim's replayable accepted disposition; complete its single-task step and output; archive only the physical message owned by that claim; increment durable requested generation and convert clean work to pending. Preserve existing retry-wait/attention state, future retry deadline, failure count/history, and pending episode metadata; new settlement must not make poison work immediately eligible or clear attention. These effects commit or roll back together.
Never release B's dependency, create B's tasks, evaluate descendants, invoke the old cascade, or finalize the run here. A success when no reconciler runs must still commit. For B success the run remains started until later reconciliation. Optional event-send/notification failure must not roll back accepted core work.
Enabled failure and size outcomes
An actual handler failure is immediately exhausted in the reviewed maxAttempts=1, whenExhausted='fail' envelope. Persist the failure once, fail culprit task/step and only started -> failed run, terminalize unfinished tasks, revoke other claims, and archive bounded owned messages synchronously. Completed work remains completed. Mark terminal work caught up consistently. This path cannot depend on a reconciler.
An oversized handler success uses #700's durable non-retryable disposition and bounded canonical replay evidence. It must not raise a graph/size exception that re-enters handler failure or repeat the successful handler. Retained payload/error/history and output aggregation stay inside the proof bounds. Use the same terminal cleanup contract for enabled fatal sources.
No retryable handler-failure scheduling, skip/skip-cascade, map-output validation, general schema validation, provider retry override, or worker-loss business exhaustion in this slice. Reject unsupported first-submission kinds without mutation; already-settled wrong kinds still conflict. Do not count infrastructure losses as handler failures.
Acceptance
- Commit A while the reconciler is absent; output/archive/pending work exist together and B remains untouched. Rollback before commit leaves none of those effects.
- Lose a committed success/failure response and resubmit: return the exact durable disposition; no second counter, generation, archive, or event effect.
- Same claim with changed kind or output conflicts; reordered JSON object keys replay;
0,false,"", null remain valid/distinct values. - Unknown/revoked claim, terminal run, wrong task identity, and stale callback after takeover cannot mutate or archive the replacement message. Test same numeric IDs across private queues.
- This issue owns and must pass the real separate-session takeover/settlement race handed off by the completed Dirty Runs: implement fenced A → B claims and controlled lease takeover #703, including both locked-winner schedules and post-lock rechecks. Dirty Runs: persist replayable settlements without downstream orchestration #704 cannot close on mocked settlement or hand the mandatory race forward; Dirty Runs: connect the typed worker path and close the A → B evidence gate #706 retains the resulting combined evidence.
- Fatal handler/size outcomes stop admission and preserve terminal state with the processor absent; identical replay on terminal run succeeds without cleanup twice.
- Archive/constraint failure rolls back acceptance AND pending work. Optional realtime failure does not. Valid current settlement while attention persists the result without clearing attention.
- A current claim settling while work is in retry-wait adds its durable generation but preserves the future retry deadline and failure history. Reconciliation stays ineligible until the deadline; settlement cannot reset backoff.
Use focused pgTAP and actual PGMQ. No general effects ledger or event outbox.
Activity
fix(worker): preserve outcomes across result reporting failures (#708)