test(host): pin the plain-JSON fold-state invariant on the contextHeaders unit
The headers fold helper now asserts every folded state is lossless JSON via the framework's own snapshotJsonValue, matching the timeline specs; a new hostile case covers the wrong-shaped plain JSON a committed log can hold and pins verbatim survival of empty-object tool schemas. A comment on the verbatim schema entry documents its session.append purification dependency.
This commit is contained in:
1 parent
2e54440297
commit
a8cd10199e
2 files changed
+30
No files matched your search
@@ -68,6 +68,8 @@ function recordOf(event: SessionEvent): HeaderRecord | null {
|
||||
const entry: HeaderTool = {
|
||||
name: typeof tool.name === 'string' ? tool.name : '?',
|
||||
tokens: estimateToolSchema(t),
|
||||
// The raw entry rides state verbatim: committed event data is already the harness's
|
||||
// lossless-JSON snapshot (session.append), so it can carry no non-JSON value.
|
||||
schema: t,
|
||||
}
|
||||
if (typeof tool.description === 'string' && tool.description !== '') {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { describe, test } from 'vitest'
|
||||
import { createContextHeadersDefinition } from '../../src/host/headers'
|
||||
import type { HeadersState } from '../../src/host/headers'
|
||||
import { header, foreign } from './helpers/events'
|
||||
import { assertPlainJson } from './helpers/projection'
|
||||
import type { TimelineEvent } from '../../src/host/fold'
|
||||
|
||||
type Def = ReturnType<typeof createContextHeadersDefinition>
|
||||
@@ -28,9 +29,11 @@ function headerEvent(seq: number, rawHeader: unknown): TimelineEvent {
|
||||
return { type: 'request/header', seq, time: seq * 1000, data: { header: rawHeader, reason: 'initial' } }
|
||||
}
|
||||
|
||||
/** Fold events through the unit, pinning the plain-JSON state precondition on every result. */
|
||||
function fold(def: Def, events: TimelineEvent[]): HeadersState {
|
||||
let state = def.init()
|
||||
for (const ev of events) state = def.apply(state, ev as never)
|
||||
assertPlainJson(state)
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -48,6 +51,7 @@ describe('createContextHeadersDefinition', () => {
|
||||
tools: [{ name: 'bash', description: 'run a command' }],
|
||||
}) as never)
|
||||
assert.notEqual(state, init, 'a header event produces a new state')
|
||||
assertPlainJson(state)
|
||||
assert.equal(state.headers.length, 1)
|
||||
assert.equal(state.headers[0].seq, 1)
|
||||
assert.equal(state.headers[0].system, 'You are an agent.')
|
||||
@@ -228,4 +232,28 @@ describe('hostile tool entries', () => {
|
||||
assert.ok('schema' in tool, 'the raw schema rides the record')
|
||||
}
|
||||
})
|
||||
|
||||
test('hostile shapes a committed log can hold keep the state lossless JSON', () => {
|
||||
// Committed event data is already the harness's lossless-JSON snapshot (session.append
|
||||
// throws on anything else), so the hostile surface here is wrong-SHAPED plain JSON —
|
||||
// never functions, undefined-valued properties, or exotic objects.
|
||||
const def = createContextHeadersDefinition()
|
||||
const toolSchema = { name: 'bash', description: 'run a command', parameters: {} }
|
||||
const state = fold(def, [headerEvent(1, {
|
||||
tools: [
|
||||
null,
|
||||
42,
|
||||
[],
|
||||
{ name: 123, extra: { deep: [true, 'x'] } }, // wrong-typed name, nested junk
|
||||
toolSchema, // an empty-object parameters schema is legal and must survive verbatim
|
||||
{ name: 'read', parameters: { type: 'object', properties: {} } },
|
||||
],
|
||||
})])
|
||||
assert.deepEqual(state.headers[0].tools[4].schema, toolSchema, 'a real ToolSchema entry rides verbatim')
|
||||
assert.deepEqual(
|
||||
state.headers[0].tools[5].schema,
|
||||
{ name: 'read', parameters: { type: 'object', properties: {} } },
|
||||
'nested empty-object schemas survive verbatim',
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user