Files
buzz/desktop/src/features/sidebar/lib/multiWindowOutbox.test.mjs
Duncan 3712e6fb2c fix(sidebar): make retained legacy whole-blob replay one-shot per value
The legacy v1 shared outbox key is never deleted (it is mutable and a
concurrently-live old build may still rewrite it), so the whole-blob
resume path re-read it on every boot and republished the stale blob above
the current relay head forever. A found relay head never stopped it: a
fresh manager has no lastPublishedStore and queueing the replay freezes
publishBaseline to the just-fetched head.

Distinguish compatibility replay from permanently pending intent with a
durable per-value consumption marker, whole-blob lanes only. resumeWholeBlobOutbox
excludes the legacy record when its exact raw matches the stored marker;
a live old build rewriting the key stores a different raw and is replayed
again. The hook transfers the intent into its own v2 key (synchronous
publish) BEFORE writing the marker, so a crash in that gap replays the
blob once more rather than losing it.

Merge lanes (stars/mutes) need no marker but gained a head-subsumed gate
so a lingering legacy key does not re-drive an identical boot-time publish.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-26 22:11:44 -04:00

709 lines
26 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
// Multi-window durable-outbox safety (Carl's CHANGES_REQUESTED, finding P1).
//
// All four sidebar-sync lanes persist an unpublished edit so it survives a
// quit/community-switch inside the 2s publish debounce. localStorage offers no
// atomic compare-and-delete or transactional read-modify-write, so neither a
// single shared key nor a per-window key a window OVERWRITES can be reclaimed
// by a peer safely: the value can change between the reclaim decision-read and
// the delete (the recheck race a byte-compare narrows but cannot close).
//
// The fix keys the outbox per window AND write-once:
// `<prefix>:<pubkey>:<relay>:<nonce>:<seq>`, where the nonce is stable per
// window (sessionStorage) and `seq` is a per-window monotonic counter. A window
// NEVER rewrites a key: a new edit writes a NEW key, then deletes its own older
// keys (write-before-delete, so a crash leaves ≥1 record, never zero). Because
// records are immutable, a booting peer that proves a foreign key reclaimable
// against durable relay evidence can delete it with no recheck — nothing can
// have changed at that key since the proof. Resume enumerates ALL windows'
// keys: merge lanes (stars/mutes) fold every record (order-independent);
// whole-blob lanes (sort/sections) replay the max-`queuedAt` record, ties broken
// by key. Reclamation runs AFTER replay so a same-second record the head appears
// to supersede is consumed into pending first. Whole-blob supersession is STRICT
// (`queuedAt` < head `created_at`) so a same-second record is never dropped, and
// the legacy v1 shared key is never deleted by v2 (it is mutable; only replayed).
//
// This matrix drives the shared helpers directly through a mock Storage seam —
// no relay, no timers — so each interleaving is deterministic. The seam mocks
// both localStorage (with a read-then-mutate hook to interpose a foreign write
// between the reclaim decision-read and the delete) and sessionStorage (for the
// per-window nonce). Manager- and hook-level behavior is covered by their own
// suites; this file isolates the cross-window storage contract.
// A mock Storage. `onReadMutate(key, afterReads, fn)` runs `fn(map)` right after
// the Nth getItem of `key` returns its captured value — used to simulate a peer
// window rewriting a key in the reclaim recheck gap.
function makeStorage(initial = {}) {
const map = new Map(Object.entries(initial));
const reads = new Map();
const hooks = [];
return {
getItem(k) {
const n = (reads.get(k) ?? 0) + 1;
reads.set(k, n);
const val = map.has(k) ? map.get(k) : null;
for (const h of hooks) if (h.key === k && h.afterReads === n) h.fn(map);
return val;
},
setItem(k, v) {
map.set(k, String(v));
},
removeItem(k) {
map.delete(k);
},
clear() {
map.clear();
},
get length() {
return map.size;
},
key(i) {
return [...map.keys()][i] ?? null;
},
onReadMutate(key, afterReads, fn) {
hooks.push({ key, afterReads, fn });
},
has: (k) => map.has(k),
};
}
// Run `fn(localStorage)` with fresh mock local + session storage installed.
function withStorage(fn) {
const ls = makeStorage();
const ss = makeStorage();
const priorWindow = globalThis.window;
globalThis.window = {
...(priorWindow ?? {}),
localStorage: ls,
sessionStorage: ss,
};
try {
return fn(ls);
} finally {
if (priorWindow !== undefined) globalThis.window = priorWindow;
else delete globalThis.window;
}
}
const { normalizeRelayUrl } = await import("@/shared/lib/normalizeRelayUrl");
const { outboxWindowNonce } = await import("./sidebarSyncWatermark.ts");
const stars = await import("./channelStarsStorage.ts");
const mutes = await import("./channelMutesStorage.ts");
const sort = await import("./channelSortPreference.ts");
const sections = await import("./channelSectionsStorage.ts");
const PK = "pk";
const RELAY = "wss://relay.example.com";
const SCOPE = `${PK}:${encodeURIComponent(normalizeRelayUrl(RELAY))}`;
const PREFIX = {
stars: "buzz-channel-stars-outbox.v1",
mutes: "buzz-channel-mutes-outbox.v1",
sort: "buzz-channel-sort-outbox.v1",
sections: "buzz-channel-sections-outbox.v1",
};
// A foreign window's key: same (pubkey, relay) scope, a different nonce than
// this process's own, plus a zero-padded `seq`. `legacyKey` is the
// pre-per-window shared key (no nonce/seq).
const SEQ_WIDTH = 12;
const pad = (seq) => String(seq).padStart(SEQ_WIDTH, "0");
const foreignKey = (lane, nonce, seq) =>
`${PREFIX[lane]}:${SCOPE}:${nonce}:${pad(seq)}`;
const legacyKey = (lane) => `${PREFIX[lane]}:${SCOPE}`;
const writeAt = (ls, key, store, queuedAt) =>
ls.setItem(key, JSON.stringify({ store, queuedAt }));
const starStore = (channels) => ({ version: 1, channels });
const starEntry = (starred, updatedAt, rev) => ({ starred, updatedAt, rev });
const muteStore = (channels) => ({ version: 1, channels });
const muteEntry = (muted, updatedAt, rev) => ({ muted, updatedAt, rev });
const sortStore = (groups) => ({ version: 1, groups });
const sectionStore = (secs, assignments = {}) => ({
version: 1,
sections: secs,
assignments,
});
// ── (i) Reclaim never deletes a foreign edit written in the decision→delete gap ─
//
// Records are write-once: an owner's fresh edit lands on a NEW key, never a
// rewrite of the enumerated one. reclaimOutbox proves the enumerated (stale)
// key reclaimable and deletes it; a peer edit injected during enumeration lives
// under its own key and is evaluated on its own merits (kept when the head does
// not subsume/supersede it). We interpose that fresh write via the read-mutate
// seam to prove GC removes only the proven-stale key and never the fresh one.
test("(i) stars: reclaim deletes the proven-stale key and keeps a fresh edit written in the gap", () => {
withStorage((ls) => {
const stale = foreignKey("stars", "peerB", 0);
// Foreign edit the fetched head has already absorbed → decision = reclaim.
writeAt(ls, stale, starStore({ a: starEntry(true, 100, 1) }), 100);
// During enumeration (read of the stale key), the peer commits a FRESH edit
// to a NEW key the head does not reflect — the write-once analogue of the
// owner rewriting in the gap.
const fresh = foreignKey("stars", "peerB", 1);
ls.onReadMutate(stale, 1, (map) =>
map.set(
fresh,
JSON.stringify({
store: starStore({ z: starEntry(true, 300, 9) }),
queuedAt: 300,
}),
),
);
// Head carries `a` at (100,1) — subsumes the stale value, not the fresh one.
stars.reclaimSubsumedStarsOutbox(
PK,
RELAY,
starStore({ a: starEntry(true, 100, 1) }),
);
assert.ok(!ls.has(stale), "proven-stale key is reclaimed");
assert.ok(ls.has(fresh), "peer's fresh edit under a new key survives");
assert.deepEqual(
stars.readChannelStarsOutbox(PK, RELAY).channels.z,
starEntry(true, 300, 9),
);
});
});
test("(i) sort: reclaim deletes the proven-stale key and keeps a fresh edit written in the gap", () => {
withStorage((ls) => {
const stale = foreignKey("sort", "peerB", 0);
writeAt(ls, stale, sortStore({ dms: "alpha" }), 100);
const fresh = foreignKey("sort", "peerB", 1);
ls.onReadMutate(stale, 1, (map) =>
map.set(
fresh,
JSON.stringify({ store: sortStore({ dms: "recent" }), queuedAt: 500 }),
),
);
// Head created_at 200 strictly supersedes the queuedAt=100 stale record,
// not the interposed queuedAt=500 fresh key.
sort.reclaimSupersededSortOutbox(PK, RELAY, 200);
assert.ok(!ls.has(stale), "proven-stale key is reclaimed");
assert.ok(ls.has(fresh), "peer's fresh edit under a new key survives");
assert.equal(
sort.readChannelSortOutbox(PK, RELAY).store.groups.dms,
"recent",
);
});
});
// ── (ii) Two windows teardown/remount: every unpublished intent preserved ──────
//
// Windows A and B each persist an edit and quit; a fresh window remounts and
// enumerates both keys. Merge lanes keep BOTH; whole-blob lanes keep the newest
// (an older peer blob is LWW-superseded by definition, the documented residual).
test("(ii) stars (merge): both windows' distinct-channel edits resume", () => {
withStorage((ls) => {
writeAt(
ls,
foreignKey("stars", "A", 0),
starStore({ a: starEntry(true, 100, 1) }),
100,
);
writeAt(
ls,
foreignKey("stars", "B", 0),
starStore({ b: starEntry(true, 200, 1) }),
200,
);
const resumed = stars.readChannelStarsOutbox(PK, RELAY);
assert.deepEqual(resumed.channels.a, starEntry(true, 100, 1));
assert.deepEqual(resumed.channels.b, starEntry(true, 200, 1));
});
});
test("(ii) mutes (merge): both windows' distinct-channel edits resume", () => {
withStorage((ls) => {
writeAt(
ls,
foreignKey("mutes", "A", 0),
muteStore({ a: muteEntry(true, 100, 1) }),
100,
);
writeAt(
ls,
foreignKey("mutes", "B", 0),
muteStore({ b: muteEntry(true, 200, 1) }),
200,
);
const resumed = mutes.readChannelMutesOutbox(PK, RELAY);
assert.deepEqual(resumed.channels.a, muteEntry(true, 100, 1));
assert.deepEqual(resumed.channels.b, muteEntry(true, 200, 1));
});
});
test("(ii) sort (whole-blob): the newest queued window resumes; older is LWW-superseded", () => {
withStorage((ls) => {
writeAt(
ls,
foreignKey("sort", "A", 0),
sortStore({ channels: "alpha" }),
100,
);
writeAt(
ls,
foreignKey("sort", "B", 0),
sortStore({ channels: "recent" }),
200,
);
assert.equal(
sort.readChannelSortOutbox(PK, RELAY).store.groups.channels,
"recent",
);
});
});
test("(ii) sections (whole-blob): the newest queued window resumes; older is LWW-superseded", () => {
withStorage((ls) => {
writeAt(
ls,
foreignKey("sections", "A", 0),
sectionStore([{ id: "s1", name: "One", order: 0 }]),
100,
);
writeAt(
ls,
foreignKey("sections", "B", 0),
sectionStore([{ id: "s2", name: "Two", order: 0 }]),
200,
);
assert.deepEqual(
sections.readChannelSectionsOutbox(PK, RELAY).store.sections,
[{ id: "s2", name: "Two", order: 0 }],
);
});
});
// ── (iii) Same-second whole-blob: head at t keeps a record queued at t ─────────
//
// One-second clock granularity means a record queued in the same second as the
// head cannot be proven to have lost LWW. Strict supersession (`queuedAt` <
// head `created_at`) keeps it; a strictly-earlier record is reclaimed.
test("(iii) sort: same-second record kept, strictly-earlier reclaimed", () => {
withStorage((ls) => {
const sameSecond = foreignKey("sort", "same", 0);
const earlier = foreignKey("sort", "old", 0);
writeAt(ls, sameSecond, sortStore({ dms: "recent" }), 100);
writeAt(ls, earlier, sortStore({ dms: "alpha" }), 99);
sort.reclaimSupersededSortOutbox(PK, RELAY, 100);
assert.ok(ls.has(sameSecond), "same-second record (queuedAt == head) kept");
assert.ok(!ls.has(earlier), "strictly-earlier record reclaimed");
});
});
test("(iii) sections: same-second record kept, strictly-earlier reclaimed", () => {
withStorage((ls) => {
const sameSecond = foreignKey("sections", "same", 0);
const earlier = foreignKey("sections", "old", 0);
writeAt(
ls,
sameSecond,
sectionStore([{ id: "s2", name: "Two", order: 0 }]),
100,
);
writeAt(
ls,
earlier,
sectionStore([{ id: "s1", name: "One", order: 0 }]),
99,
);
sections.reclaimSupersededSectionsOutbox(PK, RELAY, 100);
assert.ok(ls.has(sameSecond), "same-second record (queuedAt == head) kept");
assert.ok(!ls.has(earlier), "strictly-earlier record reclaimed");
});
});
// ── (iv) Legacy v1 shared key: replays, and is NEVER deleted by v2 ─────────────
//
// A pre-per-window build wrote one shared, MUTABLE key. v2 enumerates it as one
// more record (queuedAt 0 for a bare store) so it resumes, but never deletes it
// under any relay gating — a live old-build window may be rewriting it, and
// queuedAt 0 makes supersession meaningless (mixed dev/DMG fleet residual).
test("(iv) stars: legacy shared key resumes and is never reclaimed", () => {
withStorage((ls) => {
// Legacy bare store (no envelope) from a pre-per-window build.
ls.setItem(
legacyKey("stars"),
JSON.stringify(starStore({ a: starEntry(true, 100, 1) })),
);
const resumed = stars.readChannelStarsOutbox(PK, RELAY);
assert.deepEqual(
resumed.channels.a,
starEntry(true, 100, 1),
"legacy entry resumes",
);
// Even a head that fully subsumes it must not delete the mutable v1 key.
stars.reclaimSubsumedStarsOutbox(
PK,
RELAY,
starStore({ a: starEntry(true, 100, 1) }),
);
assert.ok(
ls.has(legacyKey("stars")),
"legacy v1 key is never deleted by v2",
);
});
});
test("(iv) sections: legacy shared key resumes and is never reclaimed", () => {
withStorage((ls) => {
// Legacy entry as a {store, queuedAt} envelope from an interim build.
writeAt(
ls,
legacyKey("sections"),
sectionStore([{ id: "s1", name: "One", order: 0 }]),
100,
);
assert.deepEqual(
sections.readChannelSectionsOutbox(PK, RELAY).store.sections,
[{ id: "s1", name: "One", order: 0 }],
);
// A head strictly past the queued stamp still must not delete the v1 key.
sections.reclaimSupersededSectionsOutbox(PK, RELAY, 999);
assert.ok(
ls.has(legacyKey("sections")),
"legacy v1 key is never deleted by v2",
);
});
});
// ── (v) Owner crash between write-new and delete-old → both replay-coalesce ────
//
// writeOwnOutbox writes the new key BEFORE deleting older own keys. A crash in
// that gap leaves two own records for the same window; replay coalesces them
// (merge fold / whole-blob max) with no loss and no duplicate publish.
test("(v) stars: two own records from a write-new/delete-old crash merge-coalesce", () => {
withStorage((ls) => {
const base = `${PREFIX.stars}:${SCOPE}:${outboxWindowNonce()}`;
// Simulate the crash residue: the pre-crash key (seq 0) plus the freshly
// written key (seq 1) both present. Merge must keep both channels.
writeAt(
ls,
`${base}:${pad(0)}`,
starStore({ a: starEntry(true, 100, 1) }),
100,
);
writeAt(
ls,
`${base}:${pad(1)}`,
starStore({ b: starEntry(true, 200, 1) }),
200,
);
const resumed = stars.readChannelStarsOutbox(PK, RELAY);
assert.deepEqual(resumed.channels.a, starEntry(true, 100, 1));
assert.deepEqual(resumed.channels.b, starEntry(true, 200, 1));
});
});
test("(v) sort: two own records from a crash resume the newer seq (padded key order)", () => {
withStorage((ls) => {
const base = `${PREFIX.sort}:${SCOPE}:${outboxWindowNonce()}`;
// Same-second seqs crossing a digit boundary: unpadded, "9" > "10"
// lexically and would wrongly resume the OLDER edit. Zero-padding makes the
// higher seq win the whole-blob tiebreak.
writeAt(ls, `${base}:${pad(9)}`, sortStore({ dms: "alpha" }), 100);
writeAt(ls, `${base}:${pad(10)}`, sortStore({ dms: "recent" }), 100);
assert.equal(
sort.readChannelSortOutbox(PK, RELAY).store.groups.dms,
"recent",
"newer seq resumes despite the digit-boundary crossing",
);
});
});
// ── (vi) Reload seeds seq above surviving own keys → no key reuse/overwrite ────
//
// After a reload the sessionStorage nonce survives but the in-memory seq counter
// restarts. A fresh write must allocate a seq ABOVE the max surviving own key so
// it never overwrites (and thus mutates) an existing immutable record.
test("(vi) reload: a write after surviving own keys allocates a strictly-higher key", () => {
withStorage((ls) => {
const base = `${PREFIX.stars}:${SCOPE}:${outboxWindowNonce()}`;
// A surviving own key from before the (simulated) reload.
writeAt(
ls,
`${base}:${pad(5)}`,
starStore({ a: starEntry(true, 50, 1) }),
50,
);
// First write of the "new session" — seq counter cold, must seed above 5.
stars.writeChannelStarsOutbox(
PK,
starStore({ b: starEntry(true, 100, 1) }),
RELAY,
);
// The pre-reload key must NOT have been overwritten; the new write lands on
// a strictly-higher key, and delete-old drops the seq-5 key.
const ownKeys = [];
for (let i = 0; i < ls.length; i++) {
const k = ls.key(i);
if (k?.startsWith(`${base}:`)) ownKeys.push(k);
}
assert.equal(ownKeys.length, 1, "delete-old leaves exactly one own key");
assert.ok(
ownKeys[0] > `${base}:${pad(5)}`,
"new key seq is strictly above the surviving key",
);
// No data loss: the newest edit resumes.
assert.deepEqual(
stars.readChannelStarsOutbox(PK, RELAY).channels.b,
starEntry(true, 100, 1),
);
});
});
// ── (vii) Whole-blob replay tie → deterministic nonce (key) tiebreak ───────────
test("(vii) sort: equal-queuedAt records resolve by key so replay is deterministic", () => {
withStorage((ls) => {
writeAt(
ls,
foreignKey("sort", "aaa", 0),
sortStore({ forums: "alpha" }),
100,
);
writeAt(
ls,
foreignKey("sort", "zzz", 0),
sortStore({ forums: "recent" }),
100,
);
// Same queuedAt → the lexicographically-greater key wins (…:zzz:…).
assert.equal(
sort.readChannelSortOutbox(PK, RELAY).store.groups.forums,
"recent",
);
});
});
// ── (viii) Merge-lane replay is order-independent ──────────────────────────────
test("(viii) stars: same-channel records fold to the max entry regardless of key order", () => {
withStorage((ls) => {
// Lower-rev record under a lexicographically-greater key (enumerated later)
// must still lose to the higher-rev record — merge is order-independent.
writeAt(
ls,
foreignKey("stars", "aaa", 0),
starStore({ c: starEntry(true, 100, 5) }),
100,
);
writeAt(
ls,
foreignKey("stars", "zzz", 0),
starStore({ c: starEntry(false, 100, 2) }),
200,
);
const merged = stars.readChannelStarsOutbox(PK, RELAY);
assert.deepEqual(
merged.channels.c,
starEntry(true, 100, 5),
"higher rev wins the tie",
);
});
});
// ── (ix) GC no-op when the head subsumes/supersedes nothing ────────────────────
//
// The hook calls reclaim only inside the `apply-remote` branch, so a `failed`
// head fetch (or `absent`) never invokes it — that guard is structural in the
// hook. At the storage layer the matching invariant is that a head which
// subsumes/supersedes nothing removes nothing: a foreign edit newer than the
// head is live intent and is kept, so a stale/empty head can never over-collect.
test("(ix) stars: a head that subsumes nothing reclaims nothing", () => {
withStorage((ls) => {
const key = foreignKey("stars", "B", 0);
writeAt(ls, key, starStore({ a: starEntry(true, 300, 2) }), 300);
// Empty head subsumes no channel → keep everything.
stars.reclaimSubsumedStarsOutbox(PK, RELAY, starStore({}));
assert.ok(ls.has(key), "unsubsumed foreign edit is kept");
});
});
test("(ix) sort: a head older than the queued edit supersedes nothing", () => {
withStorage((ls) => {
const key = foreignKey("sort", "B", 0);
writeAt(ls, key, sortStore({ dms: "recent" }), 300);
// headCreatedAt=0 (absent-equivalent) < queuedAt → keep.
sort.reclaimSupersededSortOutbox(PK, RELAY, 0);
assert.ok(ls.has(key), "edit queued after the head is kept");
});
});
// ── (x) Legacy whole-blob replay is one-shot and value-sensitive ───────────────
//
// The legacy v1 key is never deleted (it is mutable), so without a consumption
// marker `resumeWholeBlobOutbox` would return it on EVERY boot and the hook would
// republish the stale blob above the current relay head forever (Thufir pass-2
// resurrection finding). The per-value marker records the exact legacy raw a
// prior boot replayed: an unchanged legacy blob is skipped, a rewritten one (a
// live old build) is replayed again. The hook transfers the intent into its own
// v2 key (synchronous publish) BEFORE writing the marker, so a crash between the
// two replays the legacy blob once more rather than losing it.
test("(x) sort: retained legacy blob replays once, then is skipped across later boots", () => {
withStorage((ls) => {
ls.setItem(legacyKey("sort"), JSON.stringify(sortStore({ dms: "recent" })));
// Boot 1: the legacy blob resumes and reports itself for consumption.
const boot1 = sort.readChannelSortOutbox(PK, RELAY);
assert.equal(boot1.store.groups.dms, "recent", "legacy blob resumes");
assert.equal(
boot1.legacyRawToConsume,
JSON.stringify(sortStore({ dms: "recent" })),
"reports the exact legacy raw to consume",
);
// Hook: publish transfers intent to a v2 key, then marks consumed; model the
// published-and-cleared steady state (own key gone after a successful ACK).
sort.markChannelSortLegacyConsumed(PK, RELAY, boot1.legacyRawToConsume);
assert.ok(ls.has(legacyKey("sort")), "legacy key is still never deleted");
// Boot 2 and beyond: the unchanged legacy blob is excluded — no resurrection.
assert.equal(
sort.readChannelSortOutbox(PK, RELAY),
null,
"consumed legacy blob is not replayed again",
);
assert.equal(
sort.readChannelSortOutbox(PK, RELAY),
null,
"still skipped on a third boot",
);
});
});
test("(x) sections: a rewritten legacy blob (live old build) is replayed again", () => {
withStorage((ls) => {
writeAt(
ls,
legacyKey("sections"),
sectionStore([{ id: "s1", name: "One", order: 0 }]),
0,
);
const boot1 = sections.readChannelSectionsOutbox(PK, RELAY);
assert.deepEqual(boot1.store.sections, [
{ id: "s1", name: "One", order: 0 },
]);
sections.markChannelSectionsLegacyConsumed(
PK,
RELAY,
boot1.legacyRawToConsume,
);
assert.equal(
sections.readChannelSectionsOutbox(PK, RELAY),
null,
"consumed blob skipped",
);
// A live old build rewrites the legacy key with NEW intent (different raw).
writeAt(
ls,
legacyKey("sections"),
sectionStore([{ id: "s2", name: "Two", order: 0 }]),
0,
);
const boot3 = sections.readChannelSectionsOutbox(PK, RELAY);
assert.deepEqual(
boot3.store.sections,
[{ id: "s2", name: "Two", order: 0 }],
"changed legacy value is replayed",
);
assert.ok(
boot3.legacyRawToConsume !== null,
"new legacy raw reported for a fresh consumption marker",
);
});
});
test("(x) sort: crash after v2 transfer but before the marker resumes from the v2 key", () => {
withStorage((ls) => {
ls.setItem(legacyKey("sort"), JSON.stringify(sortStore({ dms: "recent" })));
const boot1 = sort.readChannelSortOutbox(PK, RELAY);
// Hook order: publish (synchronous writeOwnOutbox → v2 key) THEN mark. Model
// a crash in that gap: the v2 key exists, the marker was never written.
sort.writeChannelSortOutbox(PK, boot1.store, RELAY);
// No markChannelSortLegacyConsumed — crash before it.
// Boot 2: marker absent, so the legacy blob (queuedAt 0) is enumerated, but
// the v2 key (queuedAt > 0) wins the whole-blob max — intent is not lost,
// and the winner is not the legacy record so nothing is re-consumed.
const boot2 = sort.readChannelSortOutbox(PK, RELAY);
assert.equal(boot2.store.groups.dms, "recent", "intent survives in v2 key");
assert.equal(
boot2.legacyRawToConsume,
null,
"winner is the v2 key, not the legacy record",
);
});
});
test("(x) stars (merge lane): a legacy blob the head subsumes needs no replay publish", () => {
withStorage((ls) => {
ls.setItem(
legacyKey("stars"),
JSON.stringify(starStore({ a: starEntry(true, 100, 1) })),
);
const outbox = stars.readChannelStarsOutbox(PK, RELAY);
// The hook skips the boot replay publish when the found head subsumes the
// fold — so a lingering never-deleted legacy key doesn't re-drive an
// identical publish on every boot.
assert.ok(
stars.isStarsStoreSubsumedBy(
outbox,
starStore({ a: starEntry(true, 100, 1) }),
),
"head-subsumed legacy fold is publish-free",
);
// A head that does NOT yet reflect the legacy click still publishes.
assert.ok(
!stars.isStarsStoreSubsumedBy(outbox, starStore({})),
"an unsubsumed legacy click still needs a publish",
);
});
});
// ── Own-key round trip: write, read, clear (single-window baseline) ────────────
test("own key: write resumes, clear removes only this window's own keys", () => {
withStorage((ls) => {
stars.writeChannelStarsOutbox(
PK,
starStore({ a: starEntry(true, 100, 1) }),
RELAY,
);
const ownBase = `${PREFIX.stars}:${SCOPE}:${outboxWindowNonce()}`;
const hasOwn = () => {
for (let i = 0; i < ls.length; i++) {
const k = ls.key(i);
if (k?.startsWith(`${ownBase}:`)) return true;
}
return false;
};
assert.ok(hasOwn(), "own edit is written under this window's nonce");
// A foreign peer key is untouched by an own-key clear.
writeAt(
ls,
foreignKey("stars", "peer", 0),
starStore({ z: starEntry(true, 9, 1) }),
9,
);
stars.clearChannelStarsOutbox(PK, RELAY);
assert.ok(!hasOwn(), "own keys cleared");
assert.ok(
ls.has(foreignKey("stars", "peer", 0)),
"foreign key untouched by own clear",
);
});
});