mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
fix(todo): validate announced session histories
This commit is contained in:
@@ -39,34 +39,57 @@ function validateTodos(value: unknown, fail: InvariantFailure): void {
|
||||
}
|
||||
|
||||
/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */
|
||||
/** Whether the committed log prefix ends inside an open turn. */
|
||||
function hasOpenTurn(events: readonly SessionEvent[]): boolean {
|
||||
let open = false
|
||||
for (const event of events) {
|
||||
if (event.type === 'turn/start') open = true
|
||||
if (event.type === 'turn/end') open = false
|
||||
}
|
||||
return open
|
||||
/** Incremental turn state for one committed session log. */
|
||||
interface TurnTrace {
|
||||
open: boolean
|
||||
}
|
||||
|
||||
/** Validate one package-owned event against its payload and committed session prefix. */
|
||||
function validateEvent(session: Session, event: SessionEvent, fail: InvariantFailure): void {
|
||||
/** Advance the trace after one event has committed. */
|
||||
function advanceTrace(trace: TurnTrace, event: SessionEvent): void {
|
||||
if (event.type === 'turn/start') trace.open = true
|
||||
if (event.type === 'turn/end') trace.open = false
|
||||
}
|
||||
|
||||
/** Validate one package-owned event against the preceding committed trace. */
|
||||
function validateEvent(event: SessionEvent, trace: TurnTrace, fail: InvariantFailure): void {
|
||||
if (event.type !== 'todo/write') return
|
||||
validateTodos(event.data.todos, fail)
|
||||
if (!hasOpenTurn(session.events.slice(0, event.seq))) {
|
||||
fail('todo/write appended outside any open turn')
|
||||
if (!trace.open) fail('todo/write appended outside any open turn')
|
||||
}
|
||||
|
||||
/** Validate one existing log in a single pass and return its tail trace. */
|
||||
function seedTrace(session: Session, fail: InvariantFailure): TurnTrace {
|
||||
const trace: TurnTrace = { open: false }
|
||||
for (const event of session.events) {
|
||||
validateEvent(event, trace, fail)
|
||||
advanceTrace(trace, event)
|
||||
}
|
||||
return trace
|
||||
}
|
||||
|
||||
/** Install validation for loaded and newly appended whole-list todo snapshots. */
|
||||
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
|
||||
for (const session of ctx.sessions.list()) {
|
||||
for (const event of session.events) validateEvent(session, event, fail)
|
||||
const traces = new WeakMap<Session, TurnTrace>()
|
||||
const seed = (session: Session): void => {
|
||||
traces.set(session, seedTrace(session, fail))
|
||||
}
|
||||
const traceFor = (session: Session): TurnTrace => {
|
||||
let trace = traces.get(session)
|
||||
if (trace === undefined) {
|
||||
trace = seedTrace(session, fail)
|
||||
traces.set(session, trace)
|
||||
}
|
||||
return trace
|
||||
}
|
||||
for (const session of ctx.sessions.list()) seed(session)
|
||||
ctx.on('session/created', (session) => { seed(session) }, { global: true })
|
||||
ctx.on('internal/dispatch', (_mode, eventName, args) => {
|
||||
if (eventName !== 'session/event') return
|
||||
const [session, event] = args as [Session, SessionEvent]
|
||||
validateEvent(session, event, fail)
|
||||
validateEvent(event, traceFor(session), fail)
|
||||
}, { global: true })
|
||||
ctx.on('session/event', (session, event) => {
|
||||
advanceTrace(traceFor(session), event)
|
||||
}, { global: true })
|
||||
}, { inject: ['sessions'] })
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ToolRuntime from '@deepseek-ai/dsh-tools'
|
||||
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
|
||||
import * as TodoInvariant from '@deepseek-ai/dsh-tool-todo/invariant'
|
||||
@@ -96,4 +96,32 @@ describe('todo snapshot invariants', () => {
|
||||
|
||||
await expect(ctx.plugin(TodoInvariant).then(() => undefined)).rejects.toThrow(/repeats content "duplicate"/)
|
||||
})
|
||||
|
||||
it('validates seeded sessions announced after companion installation', async () => {
|
||||
const ctx = await setup()
|
||||
const valid = ctx.sessions.create(SessionId('todo-seeded-valid'), { seed: [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
|
||||
{ type: 'todo/write', seq: 1, time: 2, data: { todos: [] } },
|
||||
] })
|
||||
expect(() => valid.append('todo/write', { todos: [] })).not.toThrow()
|
||||
|
||||
expect(() => ctx.sessions.create(SessionId('todo-seeded-invalid'), { seed: [
|
||||
{ type: 'todo/write', seq: 0, time: 1, data: { todos: [] } },
|
||||
] })).toThrow(/outside any open turn/)
|
||||
})
|
||||
|
||||
it('tracks events committed before a prepared session is announced', async () => {
|
||||
const ctx = await setup()
|
||||
const session = ctx.sessions.prepare(SessionId('todo-prepared'))
|
||||
const detach = ctx.sessions.enter(session)
|
||||
try {
|
||||
session.append('turn/start', { turn: 1 })
|
||||
expect(() => session.append('todo/write', { todos: [] })).not.toThrow()
|
||||
ctx.sessions.announce(session)
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
expect(() => session.append('todo/write', { todos: [] })).toThrow(/outside any open turn/)
|
||||
} finally {
|
||||
detach()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user