Repository navigation
Dirty Runs: add real schema state and isolate v2 from legacy writers #702
Description
Parent epic: #699. Depends on #700.
Deliverable
Add the smallest additive schema foundation for the reviewed internal A → B protocol, on actual pgflow runtime tables. Keep v1 starts and public signatures/defaults intact. No toy schema, generic transition ledger, or full graph snapshots.
Schema state
- Immutable run protocol identity and structural definition identity; a live v2 run cannot read a changed definition. Existing runs remain v1. Do not reinterpret existing attempt counters silently.
- One stable work row per v2 run: generations, clean/pending/retry-wait/attention state, durable request/error/backoff timestamps/counters. Check nonnegative/ordered generations and reject wraparound. Keep it until safe run deletion.
- Per-task claim history: claim token, logical task/address, owned
(queue_name,message_id), worker diagnostic identity, lease horizon, effective policy snapshot, disposition, canonical replay evidence, and timestamps. Task points to its current claim. Prior settled results remain replayable. - Enforce real foreign keys, uniqueness, state eligibility, and bounded history/output invariants. A worker ID is not ownership. Preserve queue-scoped bigint IDs as exact decimal strings at the TypeScript boundary.
Put all source changes in pkgs/core/schemas/. Generate migrations and database types through repository tooling after focused behavior passes; do not hand-edit generated artifacts or rewrite released migrations. Intermediate migration work follows the companion migration workflow; finalize the exact artifact before the combined gate.
Legacy writer inventory and isolation
Inventory ALL supported entry points that can change v2 task/step/run/queue state, not only public callbacks: start_tasks, complete_task, fail_task, stalled recovery, cascade/dispatch/finalization helpers, startup compilation/recompile, flow deletion, queue teardown, optional pruning helpers, and related maintenance writes.
V1 entry points reject or filter v2 runs before task/message mutation. Versioned/internal v2 entry points reject v1. Unsupported direct helper calls must not bypass guards. Existing supported v1 behavior stays intact. Add narrow guards where the inventory demonstrates need, not a global trigger/audit framework.
Use the existing flow-maintenance lock to serialize start against structural changes/delete. Structural writes and queue-route/address changes through supported pgflow SQL cannot invalidate a live v2 run or claim. Block unsafe deletion/recompile/pruning while live/pending/attention work, unexpired claims, or retained replay evidence exist. Retention for attempts/work follows safe run retention; automatic production retention is deferred. Test-only reset may dispose of isolated fixtures only after participants stop.
Do not change a v2 run to v1 or route it through the old cascade. Use dedicated concrete test definitions/queues; old raw PGMQ pollers are not part of mixed-version support.
Also harden the existing maybe_complete_run() transition to started -> completed only, with a failed-run regression. Its v1 entry point still cannot finalize v2; v2 finalization belongs to #705. Do not repair unrelated taskless/map cascades in this two-step slice.
Acceptance
- Schema/migration-backed fixtures create actual runtime rows and constraints. Each v2 run has exactly one stable work row; existing v1 runs need no v2 work row.
- Concurrent start versus supported structural mutation/delete has a single safe outcome. A shape/version field without enforced freeze does not pass.
- The inventory lists every relevant writer with its guard and test; representative direct helper, recovery, callback, pruning, and wrong-protocol calls cannot mutate v2 task/queue state.
- Queue/address/protocol identities stay immutable through supported writes. Live deletion/recompile/pruning fails before message removal; supported v1 operations still pass.
- Settlement history survives later claims and terminal state within the declared replay horizon. Generations/work-row lifetime and history limits match Dirty Runs: freeze the bounded A → B protocol and proof envelope #700.
- Failed v1 runs never become completed through
maybe_complete_run(); additive upgrade/types checks pass.
Use schema-first pgTAP TDD and existing helpers. The final populated-baseline/migration and mixed-protocol evidence is assembled in #706.
Activity