fix(runner): cascade-skip downstream stateful steps when phase capability gate unmet - #2802
Conversation
When a phase-level `requires_capability` gate is unmet, the runner skips the phase but does not register it in `phaseStatefulCascades`. Downstream phases with stateful steps that reference `$context.*` from the skipped phase see no cascade trigger and execute with unresolved placeholder strings on the wire. Register capability-gated phases that contain stateful steps into `phaseStatefulCascades` with reason `not_applicable` so downstream `cascadeForPhase()` checks correctly trip `prerequisite_failed`. Closes adcontextprotocol/adcp#7115
c2dd243 to
896a0fb
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — well-scoped runner bugfix (adcp#7115).
Capability-gated phases now cascade-skip dependent steps before dispatch instead of sending unresolved $context placeholders on the wire, and report as neutral not_applicable in bundle scoring. Reviewer verified no pass-count inflation, correct scoping to capability gates only, preserved any_of/depends_on topology, and no regression to intentional expect_error malformed-vector behavior. Changeset (.changeset/calm-runners-skip-capabilities.md) is present alongside the src/lib/** changes, and new behavior is covered by test/lib/storyboard-phase-capability-cascade.test.js.
No Critical/High/Medium findings. Not gated (gated_paths: false), not high-risk, no author-team gate. Falls through to row 9.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
Clean pass with no findings. The delta since the prior approve is a single logical change: an added description on the collection field in the synced upstream schemas/registry/registry.yaml, with src/lib/registry/types.generated.ts regenerated to mirror it verbatim. Documentation/metadata only — no code behavior, wire-shape, or witness-fidelity change. Generated file matches source; no changeset needed since the change is generated/metadata-only.
Decision-table walkthrough:
- Rows 1: no critical/high findings.
- Row 2: gated_paths is false — not triggered (review_decision is REVIEW_REQUIRED, but the deterministic gate does not apply).
- Row 3: high_risk is true but the reason is
(modified), not(deleted). - Row 4: no medium findings.
- Row 5: high_risk
(modified)present, but no medium finding — not triggered. - Row 6: prior decision was
approve, notescalate. - Row 7: no no-auto-approve team match.
- Row 8: fewer than three medium findings (zero).
- Row 9: approve.
The high-risk flag fires only because schemas/registry/registry.yaml matches schemas/registry/** as a modification; with no medium-or-higher finding on that file, the modification is presumed safe per the flag-reading guidance.
Summary
requires_capabilitygate and stop dependent steps before dispatch, including non-statefulexpect_errorsteps and explicitcontext_inputs.depends_on: [], targeted unrelated dependencies, and siblingany_ofbranches remain independent; ordinary malformedexpect_errorvectors keep their existing wire behavior.not_applicableoutcomes while leaving genericnot_applicable,missing_tool, andprerequisite_failedcoverage gaps partial.Closes adcontextprotocol/adcp#7115
Test plan
npm run buildnpm run typechecknpm run lint(0 errors)node --test --test-reporter spec test/lib/storyboard-phase-capability-cascade.test.js test/lib/compliance-bundle-results.test.js test/lib/storyboard-cascade-skip-on-skip.test.js test/lib/storyboard-runner-output-contract-v2-runner.test.js(82/82 pass)git diff --checkECONNREFUSEDand are left to CI's managed services.