test(subagent): drop the vacuous fork-dedup case; align wording with the header baseline

The dedup guard pinned 'no duplicate stamp' — under the header baseline no
stamping exists, so its event-count assertion became a tautology and its
fence assertions a subset of the stale-seed case. The spec docblock claimed
the session invariants pin the stamping design (superseded); it now names
the three review-found timing threats the suite pins as distinct shapes.
Service overrideOf JSDoc shrinks to a pointer at the owning pure export.
This commit is contained in:
kingwl
2026-07-26 22:32:24 +08:00
parent 1a11c0dbcf
commit b02d3e4c03
5 changed files with 21 additions and 76 deletions

View File

@@ -152,7 +152,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
signature: 'overrideOf(session: Session): ApprovalPolicy | undefined',
jsDoc: '/**\n * A session\'s approval-policy OVERRIDE — the override chain alone, never\n * the configured default: the fold of the session\'s OWN switches (events\n * past the seed boundary — a fork seed\'s stale parent switch is subsumed by\n * the baseline captured after it), else the header\'s inherited delegation\n * baseline. The subagent driver stamps `overrideOf(parent.session)` into\n * each child\'s creation meta, so a `\'never\'` (headless/CI) parent cannot\n * mint children that fall back to a prompting default, at any depth\n * ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).\n * @param session - the session whose override chain to resolve.\n * @returns the effective override, or `undefined` for a session following\n * the configured default.\n * @throws when the durable header baseline is outside the closed policy\n * vocabulary (a corrupt or foreign log; durable-boundary validation).\n */',
jsDoc: '/**\n * {@link approvalOverrideOf} surfaced on the service, for consumers that\n * reach the seam through `ctx.get(\'approval\')` (the subagent driver\'s\n * delegation capture) rather than a value import.\n * @param session - the session whose override chain to resolve.\n * @returns the effective override, or `undefined` for a session following\n * the configured default.\n */',
},
],
},
@@ -492,7 +492,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
signature: 'overrideOf(session: Session): SandboxMode | undefined',
jsDoc: '/**\n * A session\'s sandbox-mode OVERRIDE — the override chain alone, never the\n * deployment default: the fold of the session\'s OWN switches (events past\n * the seed boundary — a fork seed\'s stale parent switch is subsumed by the\n * baseline captured after it), else the header\'s inherited delegation\n * baseline. The subagent driver stamps `overrideOf(parent.session)` into\n * each child\'s creation meta, so the chain collapses one level per\n * delegation and a tightened parent binds children at any depth\n * ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).\n * @param session - the session whose override chain to resolve.\n * @returns the effective override, or `undefined` for a session following\n * the deployment default.\n * @throws when the durable header baseline is outside the closed mode\n * vocabulary (a corrupt or foreign log; durable-boundary validation).\n */',
jsDoc: '/**\n * {@link sandboxOverrideOf} surfaced on the service, for consumers that\n * reach policy through `ctx.get(\'sandboxPolicy\')` (the subagent driver\'s\n * delegation capture, pty-local) rather than a value import.\n * @param session - the session whose override chain to resolve.\n * @returns the effective override, or `undefined` for a session following\n * the deployment default.\n */',
},
],
},

View File

@@ -106,19 +106,12 @@ export class SandboxPolicyService extends Service {
}
/**
* A session's sandbox-mode OVERRIDE — the override chain alone, never the
* deployment default: the fold of the session's OWN switches (events past
* the seed boundary — a fork seed's stale parent switch is subsumed by the
* baseline captured after it), else the header's inherited delegation
* baseline. The subagent driver stamps `overrideOf(parent.session)` into
* each child's creation meta, so the chain collapses one level per
* delegation and a tightened parent binds children at any depth
* ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
* {@link sandboxOverrideOf} surfaced on the service, for consumers that
* reach policy through `ctx.get('sandboxPolicy')` (the subagent driver's
* delegation capture, pty-local) rather than a value import.
* @param session - the session whose override chain to resolve.
* @returns the effective override, or `undefined` for a session following
* the deployment default.
* @throws when the durable header baseline is outside the closed mode
* vocabulary (a corrupt or foreign log; durable-boundary validation).
*/
overrideOf(session: Session): SandboxMode | undefined {
return sandboxOverrideOf(session)

View File

@@ -8,9 +8,11 @@
*
* Red/green anchor for the delegation-bypass gap: a parent switched to
* `read-only` must not mint children that run under the (wider) deployment
* default. The stamping design is itself pinned by the mounted session
* invariants: an implementation that appends the inherited override OUTSIDE
* the child's first turn fails these suites through the turn-enclosure check.
* default. The captured overrides ride the child's creation-time header, so
* three review-found timing threats are pinned as distinct shapes: a parent
* switch racing the asynchronous creation, a veto-capable prompt-submit
* listener closing a promptless first turn, and an injection-persisted turn
* before any prompt turn opens (header asserted before the child runs).
*/
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
@@ -276,35 +278,6 @@ describe('sandbox-mode inheritance against the real fs fence', () => {
expect(ctx.sandboxPolicy.resolve({ session: child.session }).mode).toBe('read-only')
})
it('a FORK child whose seed already folds to the parent mode gets NO duplicate stamp (guard)', async () => {
const script: Script = []
const captured: Agent[] = []
const { ctx, parent } = await setupWalled(script)
registerDelegate(ctx, captured)
const blocked = join(workspace, 'fork-dedup-blocked.txt')
script.push(
() => {
setSandboxMode(parent.session, 'read-only')
return textResponse('turn one')
},
toolCallResponse('d-fork', 'delegate', { fork: true }),
toolCallResponse('c-write', 'write', { file_path: blocked, content: 'escaped' }),
textResponse('fork child done'),
textResponse('turn two done'),
)
parent.followup([{ type: 'text', text: 'turn one' }])
await parent.whenIdle()
parent.followup([{ type: 'text', text: 'turn two: delegate' }])
await parent.whenIdle()
const child = captured[0] as Agent
// The seed-carried override keeps enforcing…
await expect(readFile(blocked, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' })
expect(ctx.sandboxPolicy.resolve({ session: child.session }).mode).toBe('read-only')
// …and inheritance did not append a redundant copy on top of it.
expect(overrideEvents(child).sandbox).toBe(1)
})
it('inherits the mode AT delegation, not a parent switch racing child creation', async () => {
const script: Script = []
const captured: Agent[] = []
@@ -371,7 +344,7 @@ describe('sandbox-mode inheritance against the real fs fence', () => {
})
describe('inheritance survives prompt vetoes', () => {
it('stamps the child even when an earlier-registered prompt-submit listener vetoes without next()', async () => {
it('keeps the baseline when an earlier-registered prompt-submit listener vetoes without next()', async () => {
const script: Script = []
const { ctx, parent } = await setupWalled(script)
// A veto-capable listener registered BEFORE the child exists — the
@@ -409,7 +382,7 @@ describe('inheritance survives prompt vetoes', () => {
})
describe('inheritance guards (must hold before AND after the fix)', () => {
it('a child of an unswitched parent runs under the live deployment default, with ZERO stamped events', async () => {
it('a child of an unswitched parent runs under the live deployment default, with NO baseline or events', async () => {
const script: Script = []
const { parent } = await setupWalled(script)
const allowed = join(workspace, 'default-allowed.txt')

View File

@@ -350,19 +350,12 @@ export class ApprovalService extends Service {
}
/**
* A session's approval-policy OVERRIDE — the override chain alone, never
* the configured default: the fold of the session's OWN switches (events
* past the seed boundary — a fork seed's stale parent switch is subsumed by
* the baseline captured after it), else the header's inherited delegation
* baseline. The subagent driver stamps `overrideOf(parent.session)` into
* each child's creation meta, so a `'never'` (headless/CI) parent cannot
* mint children that fall back to a prompting default, at any depth
* ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
* {@link approvalOverrideOf} surfaced on the service, for consumers that
* reach the seam through `ctx.get('approval')` (the subagent driver's
* delegation capture) rather than a value import.
* @param session - the session whose override chain to resolve.
* @returns the effective override, or `undefined` for a session following
* the configured default.
* @throws when the durable header baseline is outside the closed policy
* vocabulary (a corrupt or foreign log; durable-boundary validation).
*/
overrideOf(session: Session): ApprovalPolicy | undefined {
return approvalOverrideOf(session)