mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
persistence: include the seed boundary in the adoption policy-identity check
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).
This commit is contained in:
@@ -117,7 +117,10 @@ async function settledErrors(promises: Iterable<Promise<unknown>>): Promise<unkn
|
||||
* Reject a stored/live pair whose immutable policy baselines differ.
|
||||
* Adoption and ownerless claims retain the STORED header, so accepting a
|
||||
* conflicting pair would let a session run under its live baseline now but
|
||||
* resume under the stored one later — a silent policy swap.
|
||||
* resume under the stored one later — a silent policy swap. When either side
|
||||
* carries a baseline, the seed boundary is part of the policy identity too:
|
||||
* `overrideOf()` folds own switches past `seedLength`, so differing
|
||||
* boundaries make the same log resolve different policies across a restart.
|
||||
*/
|
||||
function assertSamePolicyBaselines(id: SessionId, stored: SessionHeader, live: SessionHeader): void {
|
||||
if (stored.sandboxMode !== live.sandboxMode || stored.approvalPolicy !== live.approvalPolicy) {
|
||||
@@ -127,6 +130,13 @@ function assertSamePolicyBaselines(id: SessionId, stored: SessionHeader, live: S
|
||||
+ `live: ${String(live.sandboxMode)}/${String(live.approvalPolicy)}) (id collision)`,
|
||||
)
|
||||
}
|
||||
const hasBaseline = stored.sandboxMode !== undefined || stored.approvalPolicy !== undefined
|
||||
if (hasBaseline && (stored.seedLength ?? 0) !== (live.seedLength ?? 0)) {
|
||||
throw new Error(
|
||||
`session "${id}" is already persisted with a different policy seed boundary `
|
||||
+ `(persisted seedLength: ${String(stored.seedLength)}, live: ${String(live.seedLength)}) (id collision)`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a live session seed reproduces a persisted prefix exactly. */
|
||||
|
||||
@@ -700,6 +700,34 @@ export function runCoordinatorContract(name: string, makeFixture: () => 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)
|
||||
|
||||
Reference in New Issue
Block a user