subagent: capture overrides at delegation; stamp ahead of prompt vetoes
Review fixes (ds-review-bot on #623): - Capture-at-delegation: the driver now reads overrideOf(parent.session) for both knobs synchronously before its first await, and the prompt-submit listener stamps those captured values — a parent switch racing the child's asynchronous creation belongs to the parent's future, not the child. The inheritOverride(parent, child) service method is split into its two halves (overrideOf / stampOverride) accordingly. - Veto safety: the one-shot prompt-submit listener registers with prepend: true, so a veto-capable listener (a denying UserPromptSubmit hook) cannot close the child's first turn without the durable stamp. Both regressions are pinned red-first in inheritance.spec.ts: the delegation-vs-late-switch race (delegate tool flips the caller wider while the creation transaction is pending) and a blocking prompt-submit listener (stamp survives a promptless first turn). Service contract tests renamed to the split API; READMEs and the bilingual Agent Note updated.
This commit is contained in:
@@ -577,44 +577,39 @@ describe('approval policy (the approval/policy fold)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('inheritOverride (parent → child stamping)', () => {
|
||||
describe('delegation inheritance (overrideOf + stampOverride)', () => {
|
||||
const policyEvents = (session: Session) => session.events.filter(e => e.type === 'approval/policy')
|
||||
|
||||
function bareSession(id: string): Session {
|
||||
return new Session(SessionId(id))
|
||||
}
|
||||
|
||||
it('stamps the parent LAST override onto the child through the canonical write path', async () => {
|
||||
it('overrideOf folds to the LAST override and never falls back to the configured default', async () => {
|
||||
const ctx = await mounted()
|
||||
const parent = bareSession('sess-appr-inherit-parent')
|
||||
const child = bareSession('sess-appr-inherit-child')
|
||||
setApprovalPolicy(parent, 'never')
|
||||
|
||||
ctx.approval.inheritOverride(parent, child)
|
||||
expect(ctx.approval.overrideOf(parent)).toBe('never')
|
||||
expect(ctx.approval.overrideOf(bareSession('sess-appr-unswitched'))).toBeUndefined()
|
||||
})
|
||||
|
||||
it('stampOverride appends the captured policy through the canonical write path', async () => {
|
||||
const ctx = await mounted()
|
||||
const child = bareSession('sess-appr-inherit-child')
|
||||
|
||||
ctx.approval.stampOverride(child, 'never')
|
||||
|
||||
const stamped = policyEvents(child)
|
||||
expect(stamped).toHaveLength(1)
|
||||
expect(stamped[0]?.data).toEqual({ policy: 'never' })
|
||||
})
|
||||
|
||||
it('appends NOTHING when the parent never switched (the configured default must stay live)', async () => {
|
||||
it('stampOverride skips a child already folding to the policy (fork-seed dedup)', async () => {
|
||||
const ctx = await mounted()
|
||||
const parent = bareSession('sess-appr-default-parent')
|
||||
const child = bareSession('sess-appr-default-child')
|
||||
|
||||
ctx.approval.inheritOverride(parent, child)
|
||||
|
||||
expect(child.events).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('skips the append when the child already folds to the inherited policy (fork-seed dedup)', async () => {
|
||||
const ctx = await mounted()
|
||||
const parent = bareSession('sess-appr-dedup-parent')
|
||||
const child = bareSession('sess-appr-dedup-child')
|
||||
setApprovalPolicy(parent, 'never')
|
||||
setApprovalPolicy(child, 'never')
|
||||
|
||||
ctx.approval.inheritOverride(parent, child)
|
||||
ctx.approval.stampOverride(child, 'never')
|
||||
|
||||
expect(policyEvents(child)).toHaveLength(1)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user