fix(#2285): wire claude-orchestration Workflow backend into execute-phase (#2314)

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:
Tom Boucher
2026-07-15 19:18:28 -04:00
committed by GitHub
parent 75bedf16fd
commit ff9cb6069f
35 changed files with 1486 additions and 203 deletions

View File

@@ -34,9 +34,13 @@ gate. It is blocked-on-nothing now that the ADR-857 capability system is release
- **`role: feature`**, `runtimeCompat.supported: ["claude"]`, `tier: full`.
- **`activationKey: claude_orchestration.enabled`** — default `false`. Nothing
changes until you opt in.
- Registers at two **wired** loop points: `execute:wave:post` (into the executor)
- Registers at two **wired** loop points: `execute:wave:pre` (into the executor)
and `plan:post` (into the planner). Both are `onError: skip` and gated by the
`enabled` key.
`enabled` key. The dispatch-backend selector fires at `execute:wave:pre` — the
seam that runs immediately BEFORE a wave's agents are dispatched — because a
selector fired *after* a wave already dispatched inline (the original
`execute:wave:post` placement, [#2285]) is structurally too late to change how
dispatch happens.
## How it decides whether to activate
@@ -62,14 +66,19 @@ Workflow backend activates only when *every* gate passes; any miss degrades to
| GSD concept | Workflow primitive |
|---|---|
| Wave | `parallel()` stage barrier |
| Plan | `agent(brief, { agentType: 'gsd-executor', isolation: 'worktree' })` |
| Plan (`use_worktree` not `false`) | `agent(brief, { agentType: 'gsd-executor', isolation: 'worktree' })` |
| Plan (`use_worktree: false`) | `agent(brief, { agentType: 'gsd-executor' })` (no isolation) |
| `files_modified` overlap | forces the plans into separate sequential stages |
| Phase run id | `resumeFromRunId("<id>")` |
| Phase token cap | `budget(<tokens>)` |
Because the emitted script composes the **same** `gsd-executor` agent and
**worktree isolation** the inline path uses, it produces the same `SUMMARY.md`
artifacts and commits — the only difference is the execution vehicle.
Because the emitted script composes the **same** `gsd-executor` agent the
inline path uses, with worktree isolation applied **per plan** from the
manifest's `use_worktree` field, it produces the same `SUMMARY.md` artifacts
and commits — the only difference is the execution vehicle. `use_worktree`
mirrors execute-phase.md step 2.5's per-plan submodule safety gate exactly: a
plan that touches a submodule path is never forced into worktree isolation,
whichever backend dispatches it ([#2772]).
## The fallback contract
@@ -92,3 +101,5 @@ own runtime gate continues to no-op on non-Claude runtimes.
[#853]: https://github.com/open-gsd/gsd-core/issues/853
[#1143]: https://github.com/open-gsd/gsd-core/issues/1143
[#2772]: https://github.com/open-gsd/gsd-core/issues/2772
[#2285]: https://github.com/open-gsd/gsd-core/issues/2285

View File

@@ -55,7 +55,7 @@ points.
| `ai-integration` | feature | full | `>=1.6.0` | `plan:pre`, `verify:pre` | step, contribution, gate | first-party |
| `assumption-delta` | feature | full | `>=1.6.0` | `plan:pre` | contribution | first-party |
| `audit` | feature | full | `>=1.6.0` | — | — | first-party |
| `claude-orchestration` | feature | full | `>=1.7.0` | `plan:post`, `execute:wave:post` | contribution | first-party |
| `claude-orchestration` | feature | full | `>=1.7.0` | `plan:post`, `execute:wave:pre` | contribution | first-party |
| `code-review` | feature | full | `>=1.6.0` | `execute:post` | step | first-party |
| `drift` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post` | gate | first-party |
| `external-job` | feature | full | `>=1.7.0` | `plan:post`, `execute:wave:post` | contribution | first-party |