Files
pad/internal
xarmian 5caf11f829 fix(collab): a REST content replacement fences tabs seeded from the old body, as a restore does (BUG-3746) (#2134)
* fix(collab): a REST content replacement fences tabs seeded from the old body, as a restore does (BUG-3746)

A prune-and-replace content write (content route and patch route, via
PruneAndApply) pruned the op-log and replaced items.content, but stamped
no seed boundary. A browser that fetched body A before the write and
joined after it, with since=0 and its old content_seq, was admitted,
seeded A into the empty document and flushed it over B. Join already
force-refreshes a client whose content_seq predates the last restore
(BUG-2264); replacements now stamp that same boundary: MarkRestoreBoundary
on the write (durable last_restore_seq, in its tx) and SetLastRestoreSeq
in memory, under the item lock Join takes, so no join lands in between.

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

* test(collab): the BUG-3746 boundary test uses a collab-enabled server (BUG-3746)

testServer has no collab manager, so the PATCH took the ordinary write
path, never reached PruneAndApply, and the test failed with the fix in
place (CI on #2134). Production always runs with collab; the test now
does too. The seeded op-log row is dropped: the boundary does not depend
on it.

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

* test(collab): the patch route has its own seed-boundary leg (BUG-3746)

The patch route prunes and replaces through its own PruneAndApply call,
wired separately from the content route, so a future edit dropping
fenceSeedsBefore there would pass the content-route test. Its twin
asserts the boundary moves past the seeded body's seq to the patch's.

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

* test(collab): a REST replacement admits a tab seeded at the new seq and refuses one seeded before it, through a real join (BUG-3746)

Heron, GO-blocking on #2134: the fence must not over-fence, or every fresh
tab after a REST replace would be force-refreshed, possibly in a loop. Per
route (CONVE-19), after the replacement a tab joining with
content_seq = the new seq gets op_log_cursor, and one at seq - 1 gets
force_refresh.

Mutant: the in-memory boundary stamped one past the write fails both admit
legs ("the fence over-fences"). Counterfactual: with the fence removed, both
refusal legs fail.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr
2026-10-14 08:39:02 -04:00
..