The claude-orchestration capability (#1143) shipped registered 'active' but fully inert: detectWorkflowBackend/emitWorkflowScript had no caller outside their own CLI router, and execute-phase.md declared an execute:wave:pre hook point that the workflow body never rendered — so claude_orchestration.enabled:true had zero effect on real runs. Approach B (maintainer-chosen): - execute-phase.md now renders the execute:wave:pre hook (gsd_run loop render-hooks execute:wave:pre) at a new step 2.75, immediately before each wave's Agent() dispatch — fixing the latent dead-hook gap for any pre-wave capability. - Move the claude-orchestration contribution execute:wave:post -> execute:wave:pre (a pre-wave backend selector belongs before dispatch, not after); rename fragments/execute-wave-post.md -> execute-wave-pre.md with prose instructing the orchestrator to call resolve-wave-dispatch before step 3. Unrelated wave:post contributions (ui.safety-gate, drift, external-job, mempalace) untouched. - New .cts seam resolveWaveDispatch(input) composes detectWorkflowBackend + emitWorkflowScript into one {backend:'inline'|'workflow', ...} result; exposed as gsd-tools claude-orchestration resolve-wave-dispatch. This is a real non-CLI-router, non-test caller of both functions. Fail-closed: any gate miss (disabled, non-Claude runtime, Workflow tool absent, SDK below floor, execution_backend:inline, malformed input) or an emit failure resolves to inline with a byte-identical result shape — no regression to the default-off execute-phase path. Regression tests (tests/fix-2285-*) cover happy-path activation + SDK-floor BVA, the fail-closed gate-miss table with detectWorkflowBackend parity, a fast-check composition property, capability.json contribution assertions, and a source-contract guard that execute:wave:pre is now actually rendered. Dependent registry-shape assertions updated in-scope. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -629,17 +629,29 @@ describe('F. Real registry execute:wave:post shape — guard against accidental
|
||||
`ui.safety-gate onError must be 'halt'; got ${uiGate.onError}`);
|
||||
});
|
||||
|
||||
test('[happy] real registry: execute:wave:post has no steps and 3 contributions (claude-orchestration executor + external-job executor + mempalace capture-problems)', () => {
|
||||
test('[happy] real registry: execute:wave:post has no steps and 2 contributions (external-job executor + mempalace capture-problems)', () => {
|
||||
const point = realRegistry.byLoopPoint['execute:wave:post'];
|
||||
assert.strictEqual(point.steps.length, 0,
|
||||
`execute:wave:post steps must be empty; got ${point.steps.length}`);
|
||||
// #1143: claude-orchestration registers an execute:wave:post contribution
|
||||
// providing the Workflow-tool backend guidance (default-off, claude-only).
|
||||
assert.strictEqual(point.contributions.length, 3,
|
||||
`execute:wave:post must have 3 contributions (claude-orchestration + external-job + mempalace); got ${point.contributions.length}`);
|
||||
// #2285: claude-orchestration's dispatch-backend-selector contribution moved
|
||||
// from execute:wave:post to execute:wave:pre — wave:post fires AFTER the
|
||||
// wave already dispatched inline, too late to select a dispatch backend.
|
||||
assert.strictEqual(point.contributions.length, 2,
|
||||
`execute:wave:post must have 2 contributions (external-job + mempalace); got ${point.contributions.length}`);
|
||||
const capIds = point.contributions.map(c => c.capId).sort();
|
||||
assert.deepStrictEqual(capIds, ['claude-orchestration', 'external-job', 'mempalace'],
|
||||
`execute:wave:post contributions must be claude-orchestration + external-job + mempalace; got ${capIds.join(',')}`);
|
||||
assert.deepStrictEqual(capIds, ['external-job', 'mempalace'],
|
||||
`execute:wave:post contributions must be external-job + mempalace; got ${capIds.join(',')}`);
|
||||
});
|
||||
|
||||
test('[happy] real registry: execute:wave:pre has 1 contribution (claude-orchestration dispatch-backend selector, #2285)', () => {
|
||||
const point = realRegistry.byLoopPoint['execute:wave:pre'];
|
||||
assert.strictEqual(point.steps.length, 0,
|
||||
`execute:wave:pre steps must be empty; got ${point.steps.length}`);
|
||||
assert.strictEqual(point.contributions.length, 1,
|
||||
`execute:wave:pre must have 1 contribution (claude-orchestration); got ${point.contributions.length}`);
|
||||
const capIds = point.contributions.map(c => c.capId).sort();
|
||||
assert.deepStrictEqual(capIds, ['claude-orchestration'],
|
||||
`execute:wave:pre contributions must be claude-orchestration; got ${capIds.join(',')}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user