Files
pad/internal/models
xarmian bf14c1168a feat(store): add item_workspace_moves provenance table (TASK-2356)
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
2026-07-30 12:20:38 +00:00
..
2026-03-26 01:52:36 +00:00