From 49a4ebd4bc535e7f958d365530b6af82995ef3fa Mon Sep 17 00:00:00 2001 From: kingwl Date: Mon, 27 Jul 2026 00:16:54 +0800 Subject: [PATCH] persistence: include the seed boundary in the adoption policy-identity check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review fix (ds-review-bot on #623): matching baselines with differing seedLength still resolve different policies — overrideOf folds own switches past the boundary, so a stored seedLength: 1 marks event 0 as subsumed seed history while a live seedLength: 0 lets the same event tighten the session; adoption retained the stored header and a restart silently restored the wide baseline. When either side carries a baseline, the seed boundary is part of the policy identity and a mismatch rejects as an id collision. Red-first in the shared coordinator contract (both backends). --- .../session-persistence/src/coordinator.ts | 12 +++++++- .../tests/coordinator-contract.ts | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/session-persistence/session-persistence/src/coordinator.ts b/packages/session-persistence/session-persistence/src/coordinator.ts index 25ffad5f93..457b19eaa6 100644 --- a/packages/session-persistence/session-persistence/src/coordinator.ts +++ b/packages/session-persistence/session-persistence/src/coordinator.ts @@ -117,7 +117,10 @@ async function settledErrors(promises: Iterable>): Promise Promise< } }) + it('a live session with a DIFFERENT seed boundary cannot adopt a stored prefix when a baseline exists', async () => { + const fix = await makeFixture() + const { ctx, fiber } = await freshCtx(fix) + try { + // Same wide baseline both sides, but the stored header says event 0 is + // seed-carried (seedLength 1) while the live header says it is the + // session's OWN (seedLength 0). overrideOf() resolves policy through + // that boundary: a read-only switch at event 0 tightens the live + // session, yet a restart resumes under the stored header and the wide + // baseline silently returns. The boundary is part of the policy + // identity whenever a baseline exists. + await ctx.sessionPersistence.create({ + ...meta('seed-boundary-conflict', WORK), + sandboxMode: 'danger-full-access', + seedLength: 1, + }) + await ctx.sessionPersistence.append(SessionId('seed-boundary-conflict'), oneTurnLog()) + const live = ctx.sessions.create(SessionId('seed-boundary-conflict'), { + seed: oneTurnLog(), + meta: { cwd: WORK, sandboxMode: 'danger-full-access' }, + }) + await expect(ctx.sessions.flush(live)).rejects.toThrow(/seed boundary|id collision/) + } finally { + await fiber.dispose() + await fix.cleanup() + } + }) + it('a no-cwd ownerless state cannot be claimed by a live session WITH a cwd (cwd scope, undefined side)', async () => { const fix = await makeFixture() const { ctx, fiber } = await freshCtx(fix)