docs(web): the cold-path pin heals one of the eviction floor's four doors, not all four (TASK-2939) (#1290)

TASK-2920's `movedOutFloor` carried a paragraph saying the exposure its cap
leaves open would be closed by giving the cold path a cursor pin, at which
point "no per-id record is load-bearing at all", and that the pin was filed
rather than built. The pin shipped in d387d540 (IDEA-2924), which falsified
both halves of that paragraph and left the file telling the next reader the
mechanism does not exist.

Measured rather than reasoned: disabling `refusedByMovedOut` against the pin as
shipped fails seven of the ten legs in `localIndexMovedOutFloor.svelte.test.ts`.
The cold `/items-index` door is healed — it pins its cursor and replays the gap,
including ids whose floor the cap had already evicted. Two others cannot be,
because the eviction advances the cursor past itself in the same atomic write
and `ListMovedOutSince` is a `seq > since` query, so nothing re-delivers it:
`upsert` has no cursor and no replay at all (its other three guards are
resync-scoped or need an `existing` the hard evict removed), and the warm IDB
hydrate's reconcile drains from a `state.cursor` that same eviction advanced.
`resyncProjectionScope` fails too and is not counted: it pins as well, and the
leg asserts the end state before the replay its caller owns has run (CONVE-12).

Three further sites in the same class, all falsified by the pin's merge and
none swept at the time: `bootstrap`'s doc header and `applyDelta`'s doc both
asserted the cursor "only advances forward" (the first is now wrong outright,
the second is true only of that function's own writes), and
`localIndexInverseColdCheck.svelte.test.ts`'s header still described the cold
branch as keeping the higher cursor and never replaying. That header also now
says what the measurement showed about the file itself: with the floor disabled
all five of its legs still pass, so it no longer exercises the floor in any
load-bearing way and the sibling suite is where that coverage lives.

Comment-only. The floor stays; what changes is that these files now say which
door the pin covers and which two the floor is the last refusal for.

Claude-Session: https://claude.ai/code/session_01Xk9M5UVPdc84xL5E1mZkm8
This commit is contained in:
xarmian
2026-09-08 13:17:31 -04:00
committed by GitHub
parent 43d94a35d5
commit 90dadecb06
2 changed files with 82 additions and 20 deletions
+62 -13
View File
@@ -208,6 +208,12 @@ class WorkspaceState {
// `persistDelta` call, so a reload finds the row already gone from disk and
// the cursor already past it — there is no window on the other side to
// guard. The map guards a within-session race only.
//
// The four doors listed above are no longer equal: the cold snapshot pins
// its cursor and replays the gap (IDEA-2924), so it is healed with or
// without this map, while `upsert` and the warm hydrate are not — their
// other guards are resync-scoped, and an eviction is not a resync. Measured under
// TASK-2939 — the population is written out at `MOVED_OUT_FLOOR_CAP`.
// BOUNDED, and NOT by the `delete` on the authoritative re-add paths: that
// lift fires only when an item comes BACK, which by definition never happens
// for one that moved out permanently — so it prunes exactly the entries that
@@ -448,12 +454,45 @@ function cursorAsNum(c: string): number {
* protected, so the cap strictly reduces the exposure and never widens it — but
* it does not eliminate it.
*
* Closing it needs a different mechanism, not a bigger number: the cold path
* would have to PIN its cursor to the snapshot's the way `resyncProjectionScope`
* already does, so the replay re-delivers every eviction in the gap and no
* per-id record is load-bearing at all. That is a change to the cold path's
* cursor contract and it interacts with TASK-2906's durable monotonicity gate,
* so it is filed rather than smuggled in here — see TASK-2920's trail.
* WHAT CLOSED PART OF IT, AND WHAT DID NOT (TASK-2939, measured). The cold path
* now PINS its cursor to the snapshot's and owns the replay from it (IDEA-2924
* — see THE PIN in `bootstrap`'s stage 2), which is the mechanism this paragraph
* used to describe as unbuilt. On THAT door the exposure above is gone: the
* replay re-delivers every eviction in the gap, including ids whose floor this
* cap had already evicted, so no per-id record is load-bearing there.
*
* At two of the other three it still is, and the measurement is the reason this
* fence was kept rather than removed with the pin: disabling `refusedByMovedOut`
* against the pin as shipped fails seven of the ten legs in
* `localIndexMovedOutFloor.svelte.test.ts`. (`resyncProjectionScope` is the
* third door and is healed for the cold door's reason — it pins too. Not by
* itself, though: it leaves `pendingResync` set and its CALLER's
* `reconcileWorkspace` drains from the pinned cursor, where the cold path calls
* that loop inline.) The
* two that remain have no mechanism that could heal them, because the eviction
* advanced the cursor past ITSELF in the same atomic write and
* `ListMovedOutSince` is a `seq > since` query — so nothing re-delivers it to
* anyone:
*
* - `upsert` — an optimistic response carries no cursor and triggers no
* replay at all. That door has three other guards (`sinceEpoch`,
* `fencedIds`, `existing.seq`) and NONE of them reaches this case: the
* first two are RESYNC-SCOPED — `sinceEpoch` compares against a
* `scopeEpoch` only a resync bumps, `fencedIds` holds only what a resync
* dropped — and an eviction is not a resync, while the third cannot fire
* because the hard evict left no `existing` to compare.
* - the warm IDB hydrate — the hydrate reads the CACHED cursor, but the
* eviction has already advanced `state.cursor` past itself, and the
* reconcile drains from `state.cursor`. So the row is reinstated from the
* cache and the drain starts beyond the news that would remove it again.
* Different door, same permanence.
*
* The `resyncProjectionScope` leg fails too and is NOT evidence: that door pins
* its cursor like the cold one, and the leg asserts the end state at the moment
* the snapshot merges — before the replay that door owns has run (CONVE-12).
*
* So the residual exposure above now stands exactly where the floor does, and
* raising the number is still not the fix.
*/
const MOVED_OUT_FLOOR_CAP = 5000;
@@ -990,10 +1029,17 @@ export const localIndex = {
* IDB so the next visit is warm.
*
* Merge-not-clear semantics are preserved: in either path, rows
* are MERGED through the same per-row seq guard `upsert` uses,
* and the cursor only advances forward. An optimistic `upsert()`
* or SSE write that landed while bootstrap was in flight is
* never regressed.
* are MERGED through the same per-row seq guard `upsert` uses. An
* optimistic `upsert()` or SSE write that landed while bootstrap
* was in flight is never regressed.
*
* The CURSOR is a different matter and this used to say it only
* advances. Since IDEA-2924 the cold path PINS it to an overtaken
* snapshot's — a deliberate RAM regression, paid for by the replay
* it starts from that cursor. Rows are still never regressed; only
* the position from which the next delta is asked for is. The
* DURABLE cursor is the one that still may not go backwards
* (TASK-2906) — see `persistDelta`'s monotonicity gate.
*/
async bootstrap(
ws: string,
@@ -1456,9 +1502,12 @@ export const localIndex = {
* for that id (e.g. SSE arrived first via a different path).
*
* Rows missing `seq` (legacy snapshots before TASK-1352) pass
* through unconditionally — there's no basis to compare. The
* cursor only advances forward, so a backslide can never lose
* progress.
* through unconditionally — there's no basis to compare. This
* function's own cursor writes only advance (guard 1 drops a
* whole non-advancing batch), so a backslide can never lose
* progress HERE. That is a property of this door, not of the
* cursor: the cold path pins it backwards on an overtaken
* snapshot and replays from there (IDEA-2924).
*/
applyDelta(
ws: string,
@@ -13,13 +13,26 @@ import { localSearch } from './localSearch.svelte';
* CONSUMED, because a `moved_out` delta was applied while `/items-index` was in
* flight.
*
* Why the cold path and not the resync path: a resync PINS the cursor to the
* snapshot's (`state.cursor = resp.cursor`), so the eviction it reinstates is
* re-delivered by the caller's very next `/items-changes` and the row goes away
* again. `bootstrap`'s cold branch keeps the HIGHER cursor and then sets
* `pendingResync = false`, so the eviction is never replayed — the reinstated
* row is durable, and the same branch persists it to IDB, so it survives a
* reload.
* Why the cold path and not the resync path, AS THIS FILE WAS WRITTEN: a resync
* PINS the cursor to the snapshot's (`state.cursor = resp.cursor`), so the
* eviction it reinstates is re-delivered by the caller's very next
* `/items-changes` and the row goes away again, whereas `bootstrap`'s cold
* branch kept the HIGHER cursor and set `pendingResync = false` the eviction
* was never replayed, the reinstated row was durable, and the same branch
* persisted it to IDB so it survived a reload.
*
* THAT ASYMMETRY IS GONE. IDEA-2924 gave the cold branch the same pin and made
* it own the replay, which is what the `pins the cursor to the snapshot and
* replays from it` case below now asserts. The paragraph above is kept as the
* reason this file exists, not as a description of the code.
*
* Which means THIS file no longer exercises the floor in any load-bearing way:
* its scenario now passes through the replay whether or not `refusedByMovedOut`
* refuses anything. The floor still stands, but the doors that depend on it are
* `upsert` and the warm hydrate — neither has a replay to be healed by — and
* they are covered by the sibling suite `localIndexMovedOutFloor.svelte.test.ts`
* (measured under TASK-2939; the population is written out at
* `MOVED_OUT_FLOOR_CAP`).
*/
const ws = 'inverse-cold-test';