fix: address review round next

- subagents.history computes its projections best-effort on both arms
  (a hostile unit's fold rejection serves the page without the block,
  matching the session-list precedent) with dual-arm coverage
- the lifecycle-witness spec probes every field of the seven-key
  witness, protecting the key list itself
- list-children's own module docs catch up with the seq-gate contract,
  and the design note records the rung-two later-event window as an
  accepted, self-healing deviation of the corruption class
This commit is contained in:
imccyu
2026-08-07 00:37:25 +08:00
parent c98a754ccb
commit 3216150ae0
7 changed files with 75 additions and 12 deletions
@@ -24,6 +24,8 @@ function bench(options: {
storedChild?: false
/** Attach the child to the live session store instead of persistence only. */
liveChild?: true
/** Every registered projection unit throws on this child's payloads. */
projectionsThrow?: true
historyParent?: SessionId
} = {}) {
const parent = { id: PARENT }
@@ -60,8 +62,14 @@ function bench(options: {
const inspect = vi.fn(() => Promise.resolve({ meta: childHeader, events: childEvents }))
const liveBlock = { values: {}, asOfSeq: 3 }
const coldBlock = { values: {}, asOfSeq: 0 }
const snapshot = vi.fn(() => liveBlock)
const restore = vi.fn(() => ({ snapshot: coldBlock }))
const snapshot = vi.fn(() => {
if (options.projectionsThrow === true) throw new Error('hostile unit')
return liveBlock
})
const restore = vi.fn(() => {
if (options.projectionsThrow === true) throw new Error('hostile unit')
return { snapshot: coldBlock }
})
const ctx = new Context()
ctx.provide('agents', { get: getAgent })
ctx.provide('subagents', { listChildren, followup })
@@ -155,6 +163,29 @@ describe('subagent gateway', () => {
expect(inspect).not.toHaveBeenCalled()
})
it('serves the page without projections when a hostile unit breaks the fold', async () => {
const cold = bench({ projectionsThrow: true })
const coldResponse = await cold.api.subagents.history(request({
parentSessionId: PARENT, childSessionId: CHILD, mode: 'continuable',
}))
expect(coldResponse.result).toMatchObject({
ok: true,
value: { hasMore: false, events: [{ event: { type: 'user/message', seq: 0 } }] },
})
if (coldResponse.result.ok) expect('projections' in coldResponse.result.value).toBe(false)
const live = bench({ projectionsThrow: true, liveChild: true })
const liveResponse = await live.api.subagents.history(request({
parentSessionId: PARENT, childSessionId: CHILD, mode: 'continuable',
}))
expect(liveResponse.result).toMatchObject({
ok: true,
value: { hasMore: false, events: [{ event: { type: 'user/message', seq: 0 } }] },
})
if (liveResponse.result.ok) expect('projections' in liveResponse.result.value).toBe(false)
expect(live.snapshot).toHaveBeenCalledTimes(1)
})
it('reads one-shot history and rejects an address with the wrong mode', async () => {
const oneShot = {
kind: 'child', id: CHILD, mode: 'one-shot', label: 'batch',