mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(session-projection): checkpoint hands out detached state clones, never live cell references
The watermark cache is the registry's authoritative mutable state; a checkpoint consumer holding the live reference could corrupt every subsequent snapshot and frame through it. structuredClone at the read face (total, by the unit plain-JSON contract) pins the boundary; a mutation test proves the cache is unreachable through handed-out rows. restore and viewCheckpoint only touch caller-owned rows — no other leak path.
This commit is contained in:
@@ -233,9 +233,11 @@ export class SessionProjectionRegistry extends Service {
|
||||
* log). This is the write side of the persisted projection cache: the
|
||||
* returned rows are the `(key → {stateVersion, observedSeq, state})` part
|
||||
* of the durable `(sessionId, key, stateVersion, observedSeq, state)`
|
||||
* rows. States are the units' live references — plain JSON by the unit
|
||||
* contract, treated as immutable; a durable writer snapshots them at its
|
||||
* own boundary.
|
||||
* rows. Every `state` is a DETACHED structured clone — never the live
|
||||
* cell reference: the watermark cache is this registry's authoritative
|
||||
* mutable state, and a caller reaching the live reference could corrupt
|
||||
* every subsequent snapshot and frame through it (plain JSON by the unit
|
||||
* contract, so the clone is total).
|
||||
* @param session - the session whose unit states are checkpointed.
|
||||
* @returns one row per registered key; empty when no unit is registered.
|
||||
*/
|
||||
@@ -246,7 +248,7 @@ export class SessionProjectionRegistry extends Service {
|
||||
rows[registration.def.key] = {
|
||||
stateVersion: registration.def.stateVersion,
|
||||
observedSeq: cell.observedSeq,
|
||||
state: cell.state,
|
||||
state: structuredClone(cell.state),
|
||||
}
|
||||
}
|
||||
return rows
|
||||
|
||||
@@ -182,6 +182,19 @@ describe('SessionProjectionRegistry drive', () => {
|
||||
expect(ctx.sessionProjections.checkpoint(fresh)['test/marks']).toEqual({ stateVersion: 1, observedSeq: -1, state: null })
|
||||
})
|
||||
|
||||
it('checkpoint states are detached clones — mutating them cannot corrupt the watermark cache', async () => {
|
||||
const { ctx, session } = await harness()
|
||||
ctx.sessionProjections.register(marksUnit())
|
||||
mark(session, ['a'])
|
||||
const rows = ctx.sessionProjections.checkpoint(session)
|
||||
// Hostile (or merely careless) consumer mutates the handed-out state.
|
||||
;(rows['test/marks']?.state as { marks: string[] }).marks.push('INJECTED')
|
||||
// The registry's authoritative cell is untouched: snapshot and a fresh
|
||||
// checkpoint both still serve the committed value.
|
||||
expect(ctx.sessionProjections.snapshot(session).values['test/marks']).toEqual({ marks: ['a'] })
|
||||
expect(ctx.sessionProjections.checkpoint(session)['test/marks']?.state).toEqual({ marks: ['a'] })
|
||||
})
|
||||
|
||||
it('restoreFloor anchors one below the lowest usable watermark and at 0 for missing or mismatched rows', async () => {
|
||||
const { ctx } = await harness()
|
||||
expect(ctx.sessionProjections.restoreFloor({})).toBeUndefined() // no unit registered
|
||||
|
||||
Reference in New Issue
Block a user