mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-24 11:26:34 +00:00
bf14c1168a
Phase 1 of PLAN-2357. Durable record of "this item was copied/moved from workspace A to workspace B", backing the forward redirect (TASK-2359) and the destination's back-pointer. Implements DR-2 / DR-2a. Paired, dual-dialect, forward-only migrations (migrations/077 + pgmigrations/055). archived_source distinguishes a move from a plain copy (INTEGER on SQLite, BOOLEAN on Postgres, written through dialect.BoolToInt). source_seq is a NULLABLE per-source move ordinal that exists solely so two moves inside the same second are orderable — created_at is second-precision RFC3339, so archive -> restore -> move again would otherwise resolve to an arbitrary destination. Partial index (source_item_id, source_seq DESC) WHERE archived_source, deliberately NOT unique: restore-then-move-again legitimately repeats. The back direction IS uniquely indexed — a destination item is created by exactly one copy, in the same transaction that writes its provenance row, so a duplicate there would silently change which source the back-pointer names. Cascade is asymmetric on purpose, inverting item_collection_moves: the archived source is precisely the row whose pointer must survive, so source_item_id carries no FK at all; target_item_id cascades, because a pointer at a vanished destination is worse than no pointer. Store accessors: a tx-taking insert helper (no self-committing variant — the row must land in the copy transaction), a forward lookup returning a SET newest-first, and a back lookup. The insert rejects an archived row with no seq and a copy row with one, so DR-2a's ordering invariant is enforced at the write boundary rather than assumed. NULL ordering is normalized with COALESCE because SQLite and Postgres disagree on DESC NULL placement. Also wires workspace purge, which the two-workspace shape requires: both workspace columns are RESTRICT references, so a purge clearing only one direction would fail outright when the purged workspace sits on the other end. Tests cover insert-in-tx, forward lookup with multiple destinations ordered newest-first and scoped to one source, back lookup, rollback leaving no row, and both DR-2a criteria. The ordering and scoping tests use fixed row IDs whose lexical order contradicts the expected answer, so deleting the ordering term or the WHERE clause under test fails them on every run rather than half the time; verified by mutating the production query. Claude-Session: https://claude.ai/code/session_01E2fRi12n8rARczvdEa2LYT