feat(#1708): typed documentation-sourced #853 dispatch-flatten (ADR-1239 Phase B) (#1719)

* feat(#1708): typed documentation-sourced #853 dispatch-flatten

Graduate the #853 orchestrator-backgrounding decision from a scattered RUNTIME==='codex' prose check to a typed, documentation-sourced engine decision. Adds a backgroundDispatch dispatch sub-axis (sourced per host: codex+cursor documented true, 9 documented false, 5 undocumented), shouldFlattenDispatch(dispatch) (inline UNLESS background && backgroundDispatch, fail-closed), and a gsd_run query dispatch-should-flatten the plan/execute workflows call. Cursor is newly background-eligible per its docs (inline->background) — a documentation-justified behavior change. No RUNTIME-name residue for this decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#1708): backgroundDispatch citations in matrix + CONTEXT note

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1708): address review findings on typed dispatch-flatten

Code/adversarial review: convert the manager.md/autonomous.md Compound Action preamble from hardcoded 'On Codex' to FLATTEN-based branching (the handlers already use the query; the preamble contradicted them and was wrong for cursor); make shouldFlattenDispatch null-safe + type-honest (accepts raw 'undocumented' registry values); make backgroundDispatch a required descriptor field (matching its siblings, all 16 carry it); strengthen the config.runtime behavioral test; update the bug-853 prose-pin test + comment. Security review clean; Codex confirmed no fail-open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1708): backfill backgroundDispatch in role:runtime test fixtures

Making backgroundDispatch a required descriptor field broke role:runtime fixtures in capability-manifest-version/capability-registry/host-integration-descriptors tests that build a dispatch object without it (caught by full gsd-test, not scoped npm test). Backfill backgroundDispatch:false into the well-formed fixtures; the deliberately-malformed 'required-field' test fixture is left malformed by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1708): update fix-1521 dispatch-gating assertion to the FLATTEN gate

fix-1521 pinned the codex-specific run_in_background prose that #1708 graduated to the typed dispatch-should-flatten/FLATTEN gate. Update its assertions to verify FLATTEN=false gating (not a runtime name) + that the old RUNTIME===codex gate is gone. Caught by full gsd-test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#1708): add changeset for typed dispatch-flatten

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1708): remove stray temp PR-body file

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1708): add issue ref to bug-853 allow-test-rule annotations

ADR-456 requires every allow-test-rule exemption to carry a see #NNN reference; the source-text-is-the-product annotations added when migrating the prose assertions lacked it (lint-tests CI gate). Add (see #1708).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-25 15:37:33 -04:00
committed by GitHub
parent 481d121dd4
commit cf2e66b39e
33 changed files with 744 additions and 149 deletions

View File

@@ -68,6 +68,7 @@ function makeMinimalRuntimeCap(overrides = {}) {
maxDepth: 1,
background: false,
subagentToolkit: 'full',
backgroundDispatch: false,
},
},
...overrides,
@@ -213,6 +214,7 @@ describe('ADR-1239 validator behavioral: undocumented sentinel passes, bogus fai
maxDepth: 'undocumented',
background: 'undocumented',
subagentToolkit: 'undocumented',
backgroundDispatch: 'undocumented',
},
},
});
@@ -229,6 +231,77 @@ describe('ADR-1239 validator behavioral: undocumented sentinel passes, bogus fai
assert.strictEqual(dispatchErrors.length, 0,
`Valid boolean dispatch fields must produce no errors; got: ${dispatchErrors.join(', ')}`);
});
// Phase B: backgroundDispatch field validation
test('dispatch.backgroundDispatch:true → ZERO validator errors', () => {
const cap = makeMinimalRuntimeCap({
hostIntegration: {
...makeMinimalRuntimeCap().runtime.hostIntegration,
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: true },
},
});
const errors = validateCapability(cap, 'test-runtime');
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
assert.strictEqual(bdErrors.length, 0,
`backgroundDispatch:true must produce no errors; got: ${bdErrors.join(', ')}`);
});
test('dispatch.backgroundDispatch:false → ZERO validator errors', () => {
const cap = makeMinimalRuntimeCap({
hostIntegration: {
...makeMinimalRuntimeCap().runtime.hostIntegration,
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: false },
},
});
const errors = validateCapability(cap, 'test-runtime');
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
assert.strictEqual(bdErrors.length, 0,
`backgroundDispatch:false must produce no errors; got: ${bdErrors.join(', ')}`);
});
test('dispatch.backgroundDispatch:"undocumented" → ZERO validator errors', () => {
const cap = makeMinimalRuntimeCap({
hostIntegration: {
...makeMinimalRuntimeCap().runtime.hostIntegration,
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: 'undocumented' },
},
});
const errors = validateCapability(cap, 'test-runtime');
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
assert.strictEqual(bdErrors.length, 0,
`backgroundDispatch:"undocumented" must produce no errors; got: ${bdErrors.join(', ')}`);
});
test('dispatch.backgroundDispatch:"zzz" → produces a validator error', () => {
const cap = makeMinimalRuntimeCap({
hostIntegration: {
...makeMinimalRuntimeCap().runtime.hostIntegration,
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: 'zzz' },
},
});
const errors = validateCapability(cap, 'test-runtime');
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
assert.ok(bdErrors.length > 0,
`bogus value "zzz" for backgroundDispatch must produce a validator error`);
});
test('dispatch without backgroundDispatch key → validator error (required field — all 16 descriptors carry it)', () => {
// backgroundDispatch is now REQUIRED (matches sibling fields namedDispatch/nested/background/subagentToolkit/maxDepth).
const cap = makeMinimalRuntimeCap({
hostIntegration: {
...makeMinimalRuntimeCap().runtime.hostIntegration,
dispatch: (() => {
const d = { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch };
delete d.backgroundDispatch;
return d;
})(),
},
});
const errors = validateCapability(cap, 'test-runtime');
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
assert.ok(bdErrors.length > 0,
`Missing backgroundDispatch (required field) must produce a validator error`);
});
});
// ---------------------------------------------------------------------------
@@ -270,6 +343,7 @@ describe('Fix 3: reserved-key guard on hostIntegration and hostIntegration.dispa
maxDepth: 5,
background: true,
subagentToolkit: 'full',
backgroundDispatch: true,
},
modelMode: 'passive',
hookBus: 'host',