agent-orchestrator/packages
Harsh Batheja cc0e034ed6 fix(pipeline): legacy route verdict regression + route scope guard + depth guard
Resolves three issues caught in self-review of PR #1886:

1. **Legacy `allSucceeded`/`anySucceeded` consulted verdict** —
   `predicate.ts:fromLegacyRoutePredicate`. v1.1's evaluator checked only
   `status === "succeeded"` and ignored `verdict`. The new bridge mapped
   `allSucceeded → all_pass`, and `all_pass`'s `isStagePassing` prefers
   `verdict` over `status`. A stage with `status=succeeded, verdict=neutral`
   passed v1.1 routes but fails v1.3 routes — silent backward-compat break.

   Fix: attach a non-enumerable `LEGACY_TAG` symbol to bridged `all_pass`
   nodes; the evaluator routes through `isStageSucceededByStatus` (status
   only) for tagged predicates and `isStagePassing` (verdict-aware) for
   direct DSL callers. The tag is a Symbol-keyed prop so it doesn't leak
   through JSON serialization (legacy routes are re-bridged on every
   evaluation anyway, so the tag is regenerated rather than persisted).

2. **Unscoped DSL leaves inside `routes.when` immediately skipped the
   stage at trigger time** — `predicate.ts` + `config-schema.ts`. A leaf
   like `{ kind: "no_open_findings" }` (no `stages`) defaults to "all
   stages in the run." At trigger time `arePreconditionsTerminal` is
   trivially true (`collectReferencedStages` returns nothing to await),
   so the scheduler evaluates the predicate against still-pending stages
   — including the host stage itself — and cascade-skips it before it can
   run. Schema accepted it but operators got a silently-dead stage.

   Fix: new `validateRoutePredicateScope` walks the predicate tree and
   reports every DSL leaf lacking an explicit, non-empty `stages` scope.
   Wired into `ConfiguredPipelineSchema.superRefine` so config load
   rejects the shape with a clear message. Legacy shapes are exempt (the
   discriminated-union schema already requires `min(1)` stages on those).
   Unscoped leaves remain legal in `exitPredicates`, where "all stages"
   is a well-defined default at terminal time.

3. **`predicateDepth` returned `-Infinity` on empty combinators** —
   `predicate.ts:243-245`. `Math.max(...[])` is `-Infinity`, and the
   subsequent `depth > MAX_PREDICATE_DEPTH` check silently passes. Zod
   rejects `predicates: []` at parse time, but the TS type permits it,
   so programmatically-constructed Predicates (engine validation, tests)
   could hit this path.

   Fix: short-circuit empty combinators to depth `0`.

## Tests

11 new tests across the existing `pipeline-predicate.test.ts`:
- `allSucceeded` ignores verdict (v1.1 status-only contract)
- `anySucceeded` ignores verdict (v1.1 status-only contract)
- `predicateDepth` returns 0 on empty combinator
- `validateRoutePredicateScope` accept/reject coverage (5 tests)
- `ConfiguredPipelineSchema` route-scope enforcement (4 tests)

All 1386 / 1386 tests passing (previously 1374; +12 new).

## Public API

- New named export: `validateRoutePredicateScope` from `pipeline/index.ts`.
  Pure function, no breaking change.
2026-05-16 23:27:49 +05:30
..
ao fix(agent-plugins,lifecycle): distinguish indeterminate probe from "not found" + bump ps timeout (closes #1838) (#1839) 2026-05-14 21:50:39 +05:30
cli chore(pipelines): merge main into pipelines (40 commits) — resolve conflicts 2026-05-16 22:13:41 +05:30
core fix(pipeline): legacy route verdict regression + route scope guard + depth guard 2026-05-16 23:27:49 +05:30
integration-tests fix(cli): reap daemon children on stop+SIGINT, sweep orphans on start (closes #1848) (#1849) 2026-05-15 03:38:09 +05:30
plugins feat(lifecycle): inject failed-job/step + log tail into CI-failure agent messages (closes #1807) (#1810) 2026-05-15 05:48:45 +05:30
web fix(core): sm.list() no longer writes terminated state to disk (#1737) 2026-05-16 19:05:49 +05:30