Commit Graph

2 Commits

Author SHA1 Message Date
xarmian 87101002a2 fix(import): bundle import remaps attachment references in version history, and on a kept partial import (BUG-3655, BUG-3656) (#2156)
* fix(import): bundle import remaps attachment references in version history, and on a kept partial import (BUG-3655, BUG-3656)

BUG-3655: RemapAttachmentReferencesInWorkspace rewrote item content, fields
and comments but not item_versions, so restoring a version (a full snapshot,
or a reverse patch that re-inserts a reference) brought back the SOURCE
workspace's attachment id. Each live item's chain is now resolved against its
pre-remap body, every resolved body remapped, and the chain re-encoded
against the remapped bodies (full stays full; a patch is re-diffed, kept while
smaller). A chain that does not resolve keeps the newest-only rebase and is
logged. The BUG-3745 remap expectation changes with it: the newest version
now resolves with the new id.

BUG-3656: the final remap ran only on success, so a plain error after some
attachments were rehydrated (an oversized blob) kept a workspace whose
references named the source ids; and a remap failure was logged and the
import answered 201. One deferred door now remaps on every keep-path exit,
and a remap failure is a kept-partial error with its own marker category.

The interchange golden gains history that carries the reference below the
newest version (a full snapshot and an older reverse patch), with coverage
flags for both. Red without the store fix on the golden and both new tests;
red with the keep-path door or the error propagation mutated.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(store): a version patch that does not apply blinds only its own segment of the chain (BUG-3655, codex r1 Medium)

The remap skipped an item's whole history when one patch failed to apply, so
a full snapshot elsewhere in that chain kept the source id. A full row
restarts the walk, so rows below it are re-encoded as usual; rows without a
body have their patch text remapped as written (a same-length id swap keeps
the hunk counts). Red on the previous commit: the full snapshot, the
unresolvable patch and the rows below kept the source id.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(store): the version remap walks the chain exactly as the reader does (BUG-3655, codex r2 Medium)

ListItemVersionsResolvedPage skips a patch that fails to apply and resolves
the row below it against the same successor. The remap stopped resolving at
the failure instead, so that row's patch text was remapped raw, which misses
an id a character-level diff splits. It now walks the same way and re-diffs
every row the reader can resolve. The test asserts through the real resolver,
with a row below the failed patch whose patch does not carry the id whole: red
on both previous versions of the remap.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-14 08:05:34 -04:00
xarmian 7f342ef336 test(interchange): golden bundle fixture and a full export, import, export round-trip diff (TASK-3628) (#2125)
* test(interchange): a golden bundle round-trips through import and export, and every loss is listed (TASK-3628)

testdata/interchange/golden is a bundle laid out as files: collections with
schemas (select with terminal and abandoned options, number, date, relation),
an archived collection with a live item, a parent and children, tags, a pin,
versions, comments, links (one to a soft-deleted item), reminders (armed;
fired and acknowledged), a set-aside collaborative edit (content_state), the
onboard playbook with its builtin_origin, a convention, and an attachment
referenced from an item body.

TestInterchangeGolden_RoundTrip imports it through the HTTP bundle door,
exports it through the same door, keys every entity by a natural key, rewrites
every id (including pad-attachment refs and relation values) to that key, and
diffs. Each difference must match interchangeKnownLosses, which gives the
reason; an entry that matches nothing fails as stale. Two known losses today:
an attachment's created_at is re-stamped at import, and a link to a
soft-deleted item is exported but not imported.

PAD_UPDATE_INTERCHANGE_GOLDEN=1 regenerates the golden.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ

* test(interchange): pin coverage, exact loss counts, versions by position (TASK-3628, codex r1)

- The golden must carry each shape the round trip checks (22 named, from an
  archived collection to a link to a dropped item); a regeneration that loses
  one fails before it is written.
- Each known loss covers an exact number of differences, so a new loss at an
  allowed path fails.
- Versions are keyed by their position in the item's history: two can share a
  second and a body. The generator now writes real edit versions
  (ForceVersion; the throttle had folded the edits into none), one of them by
  an agent, and an item created by an agent.
- UUIDs match case-insensitively; regeneration is SQLite-only and swaps the
  new golden in only after it is complete.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ

* test(interchange): regeneration never leaves no golden on disk (TASK-3628, codex r2)

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ

* test(interchange): say what the golden swap guarantees (TASK-3628, codex r3)

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ
2026-10-13 16:11:27 -04:00