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

@@ -1238,6 +1238,56 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
break;
}
case 'dispatch-should-flatten': {
// #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
//
// Resolves the current runtime (GSD_RUNTIME > config.runtime > 'claude'),
// looks up registry.runtimes[id].runtime.hostIntegration.dispatch, and
// calls shouldFlattenDispatch(dispatch) from host-integration.cjs.
//
// Fail-closed: any unknown runtime, missing dispatch, or thrown error
// yields `true` (inline — the always-safe default).
//
// Output:
// --raw → prints exactly `true` or `false`
// --json → prints { runtime, shouldFlatten, dispatch }
// default → same as --raw
try {
// Resolve runtime using the same precedence as `config-get runtime`.
const { resolveRuntime } = require('./lib/runtime-slash.cjs');
const runtimeId = resolveRuntime(cwd);
// Look up dispatch from the capability registry.
const registry = require('./lib/capability-registry.cjs');
const runtimeEntry = registry.runtimes != null
? registry.runtimes[runtimeId]
: null;
const dispatch = runtimeEntry?.runtime?.hostIntegration?.dispatch ?? null;
// Call shouldFlattenDispatch from host-integration.cjs.
const hostIntegration = require('./lib/host-integration.cjs');
const shouldFlat = dispatch !== null
? hostIntegration.shouldFlattenDispatch(dispatch)
: true; // fail-closed: unknown runtime → inline
const jsonIdx = args.indexOf('--json');
if (jsonIdx !== -1) {
output({
runtime: runtimeId,
shouldFlatten: shouldFlat,
dispatch: dispatch,
}, raw);
} else {
// --raw or default: print exactly true or false
process.stdout.write(shouldFlat ? 'true' : 'false');
}
} catch {
// Fail-closed on any error: inline is always safe.
process.stdout.write('true');
}
break;
}
case 'config-new-project': {
// Phase 6 (#3575): dispatch via SDK executeForCjs when available.
const handled = _dispatchNonFamily({

View File

@@ -126,7 +126,8 @@ const capabilities = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
@@ -248,7 +249,8 @@ const capabilities = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
@@ -330,7 +332,8 @@ const capabilities = {
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -389,7 +392,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only"
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -535,7 +539,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -604,7 +609,8 @@ const capabilities = {
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
@@ -673,7 +679,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -758,7 +765,8 @@ const capabilities = {
"nested": true,
"maxDepth": 2,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
@@ -950,7 +958,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": "undocumented",
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -1060,7 +1069,8 @@ const capabilities = {
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only"
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -1203,7 +1213,8 @@ const capabilities = {
"nested": true,
"maxDepth": -1,
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -1275,7 +1286,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -1585,7 +1597,8 @@ const capabilities = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
@@ -1789,7 +1802,8 @@ const capabilities = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -2107,7 +2121,8 @@ const capabilities = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "engine",
@@ -2262,7 +2277,8 @@ const capabilities = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": "undocumented",
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
@@ -3062,7 +3078,8 @@ const runtimes = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
@@ -3147,7 +3164,8 @@ const runtimes = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
@@ -3229,7 +3247,8 @@ const runtimes = {
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -3288,7 +3307,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only"
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -3373,7 +3393,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -3442,7 +3463,8 @@ const runtimes = {
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
@@ -3511,7 +3533,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -3596,7 +3619,8 @@ const runtimes = {
"nested": true,
"maxDepth": 2,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
@@ -3669,7 +3693,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": "undocumented",
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -3738,7 +3763,8 @@ const runtimes = {
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only"
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -3829,7 +3855,8 @@ const runtimes = {
"nested": true,
"maxDepth": -1,
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
@@ -3901,7 +3928,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -3987,7 +4015,8 @@ const runtimes = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
@@ -4060,7 +4089,8 @@ const runtimes = {
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full"
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
@@ -4128,7 +4158,8 @@ const runtimes = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "engine",
@@ -4188,7 +4219,8 @@ const runtimes = {
"nested": "undocumented",
"maxDepth": "undocumented",
"background": "undocumented",
"subagentToolkit": "undocumented"
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",

View File

@@ -1188,6 +1188,18 @@ function validateRuntimeBody(cap) {
'runtime.hostIntegration.dispatch.maxDepth must be an integer >= -1 or "undocumented" (got: ' + JSON.stringify(d.maxDepth) + ')',
);
}
// backgroundDispatch — REQUIRED (all 16 runtime descriptors carry it, matching the sibling fields
// namedDispatch/nested/background/subagentToolkit/maxDepth which are all required).
if (!Object.prototype.hasOwnProperty.call(d, 'backgroundDispatch')) {
errors.push(
'runtime.hostIntegration.dispatch.backgroundDispatch is required (must be a boolean or "undocumented")',
);
} else if (typeof d.backgroundDispatch !== 'boolean' && d.backgroundDispatch !== 'undocumented') {
errors.push(
'runtime.hostIntegration.dispatch.backgroundDispatch must be a boolean or "undocumented" (got: ' + JSON.stringify(d.backgroundDispatch) + ')',
);
}
}
}