Files
pad/internal/store/pgmigrations/044_status_transitions_seq.sql
T
xarmian 333f509274 fix(store): monotonic seq tiebreak for same-second status transitions (TASK-1643) (#655)
* fix(store): monotonic seq tiebreak for same-second status transitions (TASK-1643)

Resolves the documented historical-snapshot precision edge: created_at is
second-precision and ids are random UUIDs, so "latest transition <= T" was
nondeterministic for 2+ same-second hops on one item. Add a monotonic `seq`
(MAX+1 at insert, like items.seq; assigned inside the workspace seq lock so
per-item order is serialized — cross-row dupes are harmless) and order the
as-of-T reconstruction by created_at DESC, seq DESC, id DESC.

- migrations 065 / 044: add seq + backfill existing rows chronologically
  (ROW_NUMBER over created_at,id), dual-dialect.
- seq set on all four insert sites (update/move/create-seed hooks + backfill).
- reportSnapshotAsOf ORDER BY uses seq; doc caveat updated (now fixed).
- Test inserts same-created_at rows where id-order and seq-order DISAGREE,
  proving seq is the tiebreak.

Parent: PLAN-1628.

* fix(store): insert backfill create-seeds before hops so seq stays chronological per Codex review (round 1)

The backfill inserted activity hops first, then create-seeds, so a seed got a
HIGHER seq than a same-second hop — and since the as-of query orders by
created_at DESC, seq DESC, the seed (the initial state) wrongly won "latest"
for same-second create-and-change histories. Buffer the hops during the
activity scan and insert them AFTER the seeds, so every hop's seq exceeds its
item's seed seq (a seed is always chronologically first; on a created_at tie
the hop's higher seq correctly wins). Adds a seed-seq-below-hop test.
2026-05-29 18:56:13 -04:00

20 lines
794 B
SQL

-- Migration 044 (Postgres): monotonic ordering column for status_transitions
-- (PLAN-1628 / TASK-1643). Postgres counterpart to
-- migrations/065_status_transitions_seq.sql.
--
-- `seq` (MAX(seq)+1 at insert, like items.seq) gives a stable insertion-order
-- tiebreak for the as-of-T snapshot's "latest transition <= T" resolution,
-- which is otherwise nondeterministic for 2+ same-second hops on one item.
-- Backfill existing rows chronologically; new inserts continue from MAX(seq).
ALTER TABLE status_transitions ADD COLUMN IF NOT EXISTS seq INTEGER NOT NULL DEFAULT 0;
WITH ordered AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn
FROM status_transitions
)
UPDATE status_transitions
SET seq = ordered.rn
FROM ordered
WHERE ordered.id = status_transitions.id;