@ai-dossier/sched 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -331,7 +331,20 @@ awaiting-merge (CONFLICTING | auto-merge-blocked)
331
331
  commits were actually found and reverted — the batch is PRESERVED instead: trimmed to
332
332
  its survivors and carried straight to `reviewing`, only the evicted members requeue. A
333
333
  red or unreadable re-run, or an evicted member whose commits were never found on the
334
- branch, still dissolves in full.
334
+ branch, still dissolves in full. A member already named in `batch.evictions` is never
335
+ recorded twice (#595): `appendEvictions` (the one place either eviction rail —
336
+ `evictMembers` here or `evictMemberDirectly`'s no-commits path in
337
+ [Batch dispatch (#523)](#batch-dispatch-523) — appends to `evictions[]`) is a no-op
338
+ for a repeat issue and journals `eviction-duplicate` instead; the repeat requeue is
339
+ skipped with it, so a second call cannot overwrite the first eviction's
340
+ `failure_evidence` or kill a live re-dispatch of that member. **Read
341
+ `docs/agent-traps.md` before "fixing" the dissolve rule:** the dissolve trigger has
342
+ counted DISTINCT member ids since #572 (`evictedMemberIds`) and was never inflatable
343
+ by a duplicate. What a duplicate did inflate is the raw `evictions[]` array itself —
344
+ any eviction-RATE metric derived from it (RFC-0001 §E.5) and `sched status`'s eviction
345
+ column. `buildStatusReport` de-dups on read as well (`distinctEvictions`), so a
346
+ `state.json` persisted before this fix shows one row per member in both `sched status`
347
+ and `sched status --json`; the stored array is left as written.
335
348
  4. **Dissolve (AC3)** — `dissolveBatch` marks the batch `dissolved` and requeues every
336
349
  UNSHIPPED member: `full` (each as its own full-cycle run), `halved` (one or two fresh
337
350
  `forming` half-batches — a single remaining member yields one — entries retagged,
@@ -356,8 +369,11 @@ awaiting-merge (CONFLICTING | auto-merge-blocked)
356
369
  with no `anchor` or no `run_id` cannot post — the CLI requires both — so the milestone
357
370
  it could not post is journaled in full instead of vanishing.
358
371
 
359
- Twelve journal events carry the detail: `suite-failed`, `attributed`, `fix-dispatched`,
360
- `fix-resolved`, `member-evicted`, `revert-conflict`, `batch-rebased`, `batch-dissolved`,
372
+ Thirteen journal events carry the detail: `suite-failed`, `attributed`, `fix-dispatched`,
373
+ `fix-resolved`, `member-evicted`, `eviction-duplicate` (#595 — a second eviction call
374
+ named a member already in `evictions[]`; the append and the requeue are both no-ops and
375
+ the attempt is journaled rather than dropped), `revert-conflict`, `batch-rebased`,
376
+ `batch-dissolved`,
361
377
  `batch-preserved` (#563 — the dissolve threshold was crossed but the survivors' re-run
362
378
  suite came back green, so the batch ships them instead of dissolving), `batch-blocked`
363
379
  (#562 — the suite report was unreadable), `batch-split` and `milestone-post-failed`, plus
@@ -483,12 +499,18 @@ failure rails: executing → dissolving (a member self-reports blocked)
483
499
  kept on `BatchEntry.ranges` for eviction. An incremental gate (`ai-dossier cap run
484
500
  typecheck.run` / `test.focused`, when the repo has a manifest) runs after each member
485
501
  before advancing — a second, independent check that the member's self-reported "done"
486
- is real. Three-way outcome policy (#583): `task-failed` evicts the member (same rail
487
- as a self-reported block); `automation-broken`/`capability-unavailable` — the gate
488
- itself couldn't reach a verdict — block the batch instead of silently proceeding
489
- (`gate-inconclusive:<cap>`, `member_gates`/`blocked_reason` on `BatchEntry`, surfaced
490
- in `sched status`); `sched resume --batch <id>` re-runs the gate later to resolve the
491
- block once the capability is fixed.
502
+ is real. Four-way outcome policy (#583, split further by #594): a `task-failed` whose
503
+ `output_tail` carries recognizable evidence that the capability EARNED it — failing-test
504
+ output for a `test.*` capability, compiler/build errors for the others
505
+ (`hasEarnedFailureEvidence`) — evicts the member (same rail as a self-reported block);
506
+ a `task-failed` with an empty or framing-only capture proves nothing and joins
507
+ `automation-broken`/`capability-unavailable` — the gate itself couldn't reach a verdict
508
+ — on the block-the-batch path instead of silently proceeding (`gate-inconclusive:<cap>`,
509
+ `member_gates`/`blocked_reason` on `BatchEntry`, surfaced in `sched status`; the journal
510
+ detail names WHICH of the two branches fired). `sched resume --batch <id>` re-runs the
511
+ gate later to resolve the block once the capability is fixed, and applies the same
512
+ evidence bar on the recheck — a still-unevidenced `task-failed` stays blocked rather
513
+ than evicting on a resume.
492
514
  - **The batch's single slot is claimed FRESH for each live step** (a member, the tail
493
515
  agent, the report agent, a bounded fix agent) — never held across a wait. The aggregate
494
516
  suite itself runs with NO slot claimed at all (deterministic engine work, not an LLM
@@ -532,7 +554,9 @@ New journal events: `batch-setup-done`, `batch-setup-failed`, `member-advanced`,
532
554
  claim emits neither). `gate-inconclusive` (#583 — the incremental gate came back
533
555
  `automation-broken`/`capability-unavailable` rather than a definite `ok`/`task-failed`;
534
556
  sits alongside `batch-blocked` as the per-member analogue of the aggregate suite's
535
- "block, don't dissolve" precedent). Member/tail/report/fix-agent spawn, progress,
557
+ "block, don't dissolve" precedent; #594 routes an unevidenced `task-failed` here too).
558
+ `eviction-duplicate` (#595 — `evictMemberDirectly` and `evictMembers` both emit it when
559
+ asked to evict a member already in `evictions[]`). Member/tail/report/fix-agent spawn, progress,
536
560
  completion and park events reuse the existing unit-generic names (`assigned`/`spawned`/`unit-failed`/
537
561
  `external-advance`/`pr-parked`/`merge-accepted`/`report-dispatched`/`teardown-done`/
538
562
  `teardown-failed`) with `unit = batch:<id>`.
@@ -601,6 +625,11 @@ import {
601
625
  parseVitestJson, // vitest --reporter=json → failing tests
602
626
  isReadableVitestReport, // #562: a parseable { testResults: [...] } document exists —
603
627
  // distinct from "zero failures"
628
+ hasFailingTestEvidence, // #594: an output_tail that PROVES a suite ran and went red —
629
+ // an empty/framing-only capture, or a green report, is not
630
+ // a real task-failed
631
+ hasEarnedFailureEvidence, // #594: the same bar per capability — test.* is held to
632
+ // failing-test output, others may prove it with compiler errors
604
633
  parseBoundaryCommits, // git log → issue-boundary commits via the (#N) trailer
605
634
  memberRanges, // boundary commits → each member's commit list
606
635
  runAttributionBisect, // stage-2: real git bisect over the failing tests only
@@ -616,6 +645,10 @@ import {
616
645
  createExecMilestonePoster, // batch milestones via `ai-dossier runstate post`
617
646
  expandEvictionGroups, // members that must revert together (§E.4 eviction groups)
618
647
  requeueMember, // the one requeue path abandon/evict/dissolve all take
648
+ appendEvictions, // #595: the one evictions[] append — skips an issue already
649
+ // recorded, returns the duplicates so the caller can journal
650
+ distinctEvictions, // #595 read side: one record per member for a legacy state.json
651
+ duplicateEvictionDetail, // the one eviction-duplicate wording, shared by both rails
619
652
  isPreservedMember, // the single definition of "already green"
620
653
  createBatch, // the single BatchEntry constructor
621
654
  type RecoveryDeps, // inject exec/repoDir/journal/milestone-poster/suite-runner/clock
@@ -75,6 +75,48 @@ export declare function parseVitestJson(stdout: string | null): FailingTest[];
75
75
  * exit code that happens to carry no parseable body.
76
76
  */
77
77
  export declare function isReadableVitestReport(stdout: string | null): boolean;
78
+ /**
79
+ * `isReadableVitestReport` generalized to the per-member incremental gate's
80
+ * `output_tail` (#594) — a free-form capture of a capability's combined
81
+ * stdout+stderr, not necessarily a `--reporter=json` document (`test.focused`
82
+ * commonly runs a runner's default text reporter, or a wrapper script's own
83
+ * framing around one). A `task-failed` outcome is only as trustworthy as the
84
+ * evidence behind it: a vitest JSON report **naming at least one failed
85
+ * assertion**, or one of `FAILING_TEST_MARKERS`, means a suite genuinely ran
86
+ * and found something red. Anything else — an empty capture, or a body that is
87
+ * only a script's own framing (the pilot attempt 4 shape: 765 bytes of wrapper
88
+ * output and a `tee: /dev/stderr` error, zero test output) — is
89
+ * indistinguishable from a capability that fabricated its exit code.
90
+ *
91
+ * A *readable but green* JSON report is deliberately not evidence: a wrapper
92
+ * that emits a passing `--reporter=json` document and then exits 1 is the
93
+ * fabricated-exit-code shape itself, so `isReadableVitestReport` (which is
94
+ * true for zero failures by design, #562) is the wrong test here.
95
+ *
96
+ * `outputTail` absent entirely (`null`/`undefined`) is NOT the same as
97
+ * "captured and empty": some callers never populate it at all, and a caller
98
+ * that never tried to capture output has no basis to second-guess an earned
99
+ * exit code — this returns `true` (trust the outcome) in that case, matching
100
+ * every `runIncrementalGate` caller that predates `output_tail` (#583). The
101
+ * production runner never reaches that branch: it falls back to the
102
+ * subprocess's own captured output when the envelope omits `output_tail`
103
+ * (`cli/src/commands/sched.ts`), so a stale capability build cannot silently
104
+ * buy back the pre-#594 evict-on-any-`task-failed` behaviour.
105
+ */
106
+ export declare function hasFailingTestEvidence(outputTail: string | null | undefined): boolean;
107
+ /**
108
+ * The evidence bar for a `task-failed` from capability `capabilityId` (#594).
109
+ *
110
+ * The incremental gate runs `typecheck.run` alongside `test.focused`, and a
111
+ * real type error carries none of `FAILING_TEST_MARKERS` — gating it on
112
+ * test-shaped evidence would route every genuine build break to the
113
+ * block-the-batch path and wedge the batch there, since `sched resume --batch`
114
+ * would re-run the same deterministically failing typecheck forever. So a
115
+ * non-test capability may prove itself with compiler markers too; only a test
116
+ * capability is held to test output, which is where the fabricated exit codes
117
+ * were observed.
118
+ */
119
+ export declare function hasEarnedFailureEvidence(capabilityId: string, outputTail: string | null | undefined): boolean;
78
120
  /** One commit on the batch branch, with the member it belongs to. */
79
121
  export interface BoundaryCommit {
80
122
  sha: string;
@@ -1 +1 @@
1
- {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,yEAAyE;IACzE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,0DAA0D;IAC1D,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,2DAA2D;IAC3D,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAEnE;AAqCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,UAAU,EAAE,SAAS,eAAe,EAAE,GACrC,kBAAkB,CAwBpB;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAE9E;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,EAAE,CA0BpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAMrE;AAiCD,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AASD;;;;GAIG;AACH,eAAO,MAAM,MAAM,QAAsB,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,WAAW,QAAkC,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,EAAE,CAalF;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,EAAE,CAgB/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS9F"}
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,8 @@ exports.attributeByOverlap = attributeByOverlap;
23
23
  exports.offendersOf = offendersOf;
24
24
  exports.parseVitestJson = parseVitestJson;
25
25
  exports.isReadableVitestReport = isReadableVitestReport;
26
+ exports.hasFailingTestEvidence = hasFailingTestEvidence;
27
+ exports.hasEarnedFailureEvidence = hasEarnedFailureEvidence;
26
28
  exports.parseBoundaryCommits = parseBoundaryCommits;
27
29
  exports.memberRanges = memberRanges;
28
30
  exports.memberOfCommit = memberOfCommit;
@@ -157,6 +159,93 @@ function isReadableVitestReport(stdout) {
157
159
  const results = parsed.testResults;
158
160
  return Array.isArray(results);
159
161
  }
162
+ /**
163
+ * Markers a test runner prints only when a suite actually ran and went red.
164
+ *
165
+ * Every one is anchored to the start of a line (or to a digit-led count), which
166
+ * is the whole point: an unanchored `/fail/i` also matches a wrapper script's
167
+ * own prose about the run. The recorded pilot attempt-4 body ends
168
+ * `... run test exited 1 — real test failure.` — "failure" is a substring
169
+ * match, so a bare `/fail/i` accepts the exact 765-byte capture that carries
170
+ * zero test output (`docs/reports/batch-pilot-2-execution.md`), which is the
171
+ * body #594 exists to reject. Same for pnpm's universal
172
+ * `ELIFECYCLE Command failed with exit code 1.` and npm's
173
+ * `Lifecycle script \`test\` failed`.
174
+ */
175
+ const FAILING_TEST_MARKERS = [
176
+ /^\s*(?:❯\s*)?FAIL(?:ED)?\b/m, // vitest/jest per-file line
177
+ /^\s*[✗×✕●]\s/m, // vitest / jest / mocha per-test bullet
178
+ /^not ok \d+/m, // TAP
179
+ /^\s*\d+\s+(?:tests?|specs?|assertions?|examples?)?\s*fail(?:ed|ing|ures?)\b/im, // "1 failing", "2 tests failed"
180
+ /^[\s=]*(?:Tests?|Test Files|Specs?|Examples?)\b[^\n]*?\b\d+\s+fail(?:ed|ing|ures?)\b/im, // runner summary line
181
+ ];
182
+ /** Markers a compiler / build tool prints for a genuine build failure. */
183
+ const BUILD_FAILURE_MARKERS = [
184
+ /^[^\n]*\berror TS\d+/m, // tsc
185
+ /^[^\n]*\(\d+,\d+\):\s*error\b/m, // tsc / msbuild positional form
186
+ /^[^\n]*?:\d+:\d+:\s*error\b/im, // gcc/clang/esbuild/swc positional form
187
+ /^\s*Found \d+ errors?\b/im, // tsc / mypy summary
188
+ /^\s*error\[?[A-Z]?\d*\]?:/im, // rustc / generic "error: ..."
189
+ ];
190
+ /**
191
+ * `isReadableVitestReport` generalized to the per-member incremental gate's
192
+ * `output_tail` (#594) — a free-form capture of a capability's combined
193
+ * stdout+stderr, not necessarily a `--reporter=json` document (`test.focused`
194
+ * commonly runs a runner's default text reporter, or a wrapper script's own
195
+ * framing around one). A `task-failed` outcome is only as trustworthy as the
196
+ * evidence behind it: a vitest JSON report **naming at least one failed
197
+ * assertion**, or one of `FAILING_TEST_MARKERS`, means a suite genuinely ran
198
+ * and found something red. Anything else — an empty capture, or a body that is
199
+ * only a script's own framing (the pilot attempt 4 shape: 765 bytes of wrapper
200
+ * output and a `tee: /dev/stderr` error, zero test output) — is
201
+ * indistinguishable from a capability that fabricated its exit code.
202
+ *
203
+ * A *readable but green* JSON report is deliberately not evidence: a wrapper
204
+ * that emits a passing `--reporter=json` document and then exits 1 is the
205
+ * fabricated-exit-code shape itself, so `isReadableVitestReport` (which is
206
+ * true for zero failures by design, #562) is the wrong test here.
207
+ *
208
+ * `outputTail` absent entirely (`null`/`undefined`) is NOT the same as
209
+ * "captured and empty": some callers never populate it at all, and a caller
210
+ * that never tried to capture output has no basis to second-guess an earned
211
+ * exit code — this returns `true` (trust the outcome) in that case, matching
212
+ * every `runIncrementalGate` caller that predates `output_tail` (#583). The
213
+ * production runner never reaches that branch: it falls back to the
214
+ * subprocess's own captured output when the envelope omits `output_tail`
215
+ * (`cli/src/commands/sched.ts`), so a stale capability build cannot silently
216
+ * buy back the pre-#594 evict-on-any-`task-failed` behaviour.
217
+ */
218
+ function hasFailingTestEvidence(outputTail) {
219
+ if (outputTail === null || outputTail === undefined)
220
+ return true;
221
+ const text = outputTail.trim();
222
+ if (text.length === 0)
223
+ return false;
224
+ if (isReadableVitestReport(text))
225
+ return parseVitestJson(text).length > 0;
226
+ return FAILING_TEST_MARKERS.some((re) => re.test(text));
227
+ }
228
+ /**
229
+ * The evidence bar for a `task-failed` from capability `capabilityId` (#594).
230
+ *
231
+ * The incremental gate runs `typecheck.run` alongside `test.focused`, and a
232
+ * real type error carries none of `FAILING_TEST_MARKERS` — gating it on
233
+ * test-shaped evidence would route every genuine build break to the
234
+ * block-the-batch path and wedge the batch there, since `sched resume --batch`
235
+ * would re-run the same deterministically failing typecheck forever. So a
236
+ * non-test capability may prove itself with compiler markers too; only a test
237
+ * capability is held to test output, which is where the fabricated exit codes
238
+ * were observed.
239
+ */
240
+ function hasEarnedFailureEvidence(capabilityId, outputTail) {
241
+ if (hasFailingTestEvidence(outputTail))
242
+ return true;
243
+ if (capabilityId.startsWith('test.'))
244
+ return false;
245
+ if (outputTail === null || outputTail === undefined)
246
+ return true;
247
+ return BUILD_FAILURE_MARKERS.some((re) => re.test(outputTail.trim()));
248
+ }
160
249
  /** The first balanced `{...}` document in `text`, parsed; null when there is none. */
161
250
  function extractJsonObject(text) {
162
251
  const start = text.indexOf('{');
@@ -1 +1 @@
1
- {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
@@ -136,8 +136,10 @@ export interface BatchTickResult {
136
136
  *
137
137
  * - still `automation-broken`/`capability-unavailable` → stays `blocked`,
138
138
  * no state change (the capability still isn't fixed).
139
- * - `task-failed` → the member really is broken; evict via the same rail
140
- * the live gate uses.
139
+ * - `task-failed` with recognizable failing-test evidence (#594,
140
+ * `hasFailingTestEvidence`) → the member really is broken; evict via the
141
+ * same rail the live gate uses. `task-failed` with no evidence stays
142
+ * `blocked`, same as the still-inconclusive case above.
141
143
  * - `ok` → the member really was fine; complete it via the same rail the
142
144
  * live gate uses.
143
145
  *
@@ -153,6 +155,13 @@ export interface BatchTickResult {
153
155
  export declare function resumeBlockedGate(deps: BatchDispatchDeps, config: SchedConfig, dispatch: ResolvedDispatch, batchId: string, now: Date): {
154
156
  outcome: 'still-blocked' | 'evicted' | 'completed';
155
157
  capability: string;
158
+ /**
159
+ * Why the batch is still blocked, when it is. Both branches land on
160
+ * `gate-inconclusive:<cap>`, but the operator's next action differs — fix
161
+ * the capability's availability, or fix the wrapper that reports a failure
162
+ * it cannot evidence — so the caller must not have to guess from the detail.
163
+ */
164
+ blockedBy?: 'inconclusive' | 'unevidenced-failure';
156
165
  detail?: string;
157
166
  };
158
167
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAkBH,OAAO,EAUL,KAAK,gBAAgB,EAErB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAOjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAUL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,KAAK,QAAQ,EAA2D,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAGV,oBAAoB,EAIpB,WAAW,EAIZ,MAAM,SAAS,CAAC;AAMjB,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAywCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,IAAI,GACR;IAAE,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA+E7F;AAogBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsFjB"}
1
+ {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAmBH,OAAO,EAUL,KAAK,gBAAgB,EAErB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAOjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAUL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAcpB,OAAO,EAAE,KAAK,QAAQ,EAA2D,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAGV,oBAAoB,EAIpB,WAAW,EAIZ,MAAM,SAAS,CAAC;AAMjB,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA0zCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,IAAI,GACR;IACD,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,WAAW,CAAC;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,GAAG,qBAAqB,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CA2GA;AAqhBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsFjB"}
@@ -158,6 +158,25 @@ function gateDetailExcerpt(outputTail, reason) {
158
158
  const buf = Buffer.from(text, 'utf-8');
159
159
  return buf.length > 500 ? buf.subarray(buf.length - 500).toString('utf-8') : text;
160
160
  }
161
+ /** A journal detail with the gate's output excerpt appended when there is one. */
162
+ function withExcerpt(message, excerpt) {
163
+ return excerpt ? `${message}: ${excerpt}` : message;
164
+ }
165
+ /**
166
+ * Which block-the-batch branch a gate result took (#594 AC3). Both an
167
+ * `automation-broken`/`capability-unavailable` result and a `task-failed` the
168
+ * gate could not evidence land on `gate-inconclusive:<cap>`; only the detail
169
+ * distinguishes "the capability is broken" from "the capability claimed a
170
+ * failure it did not prove", and those need different operator responses.
171
+ */
172
+ function describeInconclusive(gate) {
173
+ if (gate.outcome !== 'task-failed')
174
+ return `reported ${gate.outcome}`;
175
+ const captured = gate.outputTail?.trim();
176
+ return captured
177
+ ? `reported task-failed with no failing-test evidence in its ${Buffer.byteLength(captured, 'utf-8')}-byte capture`
178
+ : 'reported task-failed with an empty capture — no evidence at all';
179
+ }
161
180
  /**
162
181
  * Best-effort per-gate diagnostic log (#583 AC1) — mirrors `appendCapLog`'s
163
182
  * never-crash contract: a log-write failure must not interrupt the gate
@@ -1006,6 +1025,13 @@ function evictMemberAndContinue(deps, config, dispatch, batchId, batch, memberIs
1006
1025
  * script that legitimately could not run its suite must not be read as
1007
1026
  * either a pass or a real failure). Only both `ok` falls through.
1008
1027
  *
1028
+ * A `task-failed` is itself split in two (#594): its `outputTail` must carry
1029
+ * recognizable evidence of a failing test (`hasFailingTestEvidence`) before
1030
+ * it is trusted as a red suite. A `task-failed` whose output proves nothing —
1031
+ * empty, or only a wrapper script's own framing — did not earn its exit
1032
+ * code, and joins the inconclusive case on the block-the-batch path instead
1033
+ * of evicting a member for a failure that never happened.
1034
+ *
1009
1035
  * Returns `true` when the gate already decided the member's fate (evicted or
1010
1036
  * blocked, both of which `return` from the caller); `false` when no hook is
1011
1037
  * configured or both checks came back `ok`, meaning the caller should treat
@@ -1020,37 +1046,48 @@ function runIncrementalGate(deps, config, dispatch, batchId, batch, memberIssue,
1020
1046
  id,
1021
1047
  ...runCapability(worktree, id),
1022
1048
  }));
1023
- const gateFailure = gateResults.find((r) => r.outcome === 'task-failed');
1049
+ const rawFailure = gateResults.find((r) => r.outcome === 'task-failed');
1024
1050
  const gateInconclusive = gateResults.find((r) => r.outcome === 'automation-broken' || r.outcome === 'capability-unavailable');
1025
- const worstGate = gateFailure ?? gateInconclusive;
1026
- if (worstGate) {
1027
- recordMemberGate(deps, batchId, memberIssue, worstGate, now);
1051
+ const earnedFailure = rawFailure && (0, attribution_1.hasEarnedFailureEvidence)(rawFailure.id, rawFailure.outputTail)
1052
+ ? rawFailure
1053
+ : undefined;
1054
+ const unevidencedFailure = rawFailure && !earnedFailure ? rawFailure : undefined;
1055
+ // Record the gate the batch is actually routed on, not merely the worst
1056
+ // outcome: an unevidenced `task-failed` alongside a genuine
1057
+ // `automation-broken` blocks on the LATTER, and `member_gates` naming the
1058
+ // former would contradict `blocked_reason` in `sched status` — and would
1059
+ // name a different capability than `resumeBlockedGate` rechecks, since that
1060
+ // parses `blocked_reason`.
1061
+ const decisiveGate = earnedFailure ?? gateInconclusive ?? unevidencedFailure;
1062
+ if (decisiveGate) {
1063
+ recordMemberGate(deps, batchId, memberIssue, decisiveGate, now);
1028
1064
  }
1029
- if (gateFailure) {
1030
- const reason = `incremental-gate-failed:${gateFailure.id}`;
1031
- writeGateLog(deps, batchId, gateFailure.id, memberIssue, gateFailure.outputTail);
1032
- const excerpt = gateDetailExcerpt(gateFailure.outputTail, gateFailure.reason);
1065
+ if (earnedFailure) {
1066
+ const reason = `incremental-gate-failed:${earnedFailure.id}`;
1067
+ writeGateLog(deps, batchId, earnedFailure.id, memberIssue, earnedFailure.outputTail);
1068
+ const excerpt = gateDetailExcerpt(earnedFailure.outputTail, earnedFailure.reason);
1033
1069
  journalEvent(deps, 'unit-failed', unit(batchId), {
1034
1070
  issue: memberIssue,
1035
1071
  reason,
1036
- detail: excerpt
1037
- ? `cap run ${gateFailure.id} reported task-failed after member review done: ${excerpt}`
1038
- : `cap run ${gateFailure.id} reported task-failed after member review done`,
1072
+ detail: withExcerpt(`cap run ${earnedFailure.id} reported task-failed with failing-test evidence after member review done`, excerpt),
1039
1073
  });
1040
1074
  deps.store.withLock((s) => ({ state: releaseSlot(s, batchId, now), result: undefined }));
1041
1075
  evictMemberAndContinue(deps, config, dispatch, batchId, batch, memberIssue, reason, now, result);
1042
1076
  return true;
1043
1077
  }
1044
- if (gateInconclusive) {
1045
- const reason = `gate-inconclusive:${gateInconclusive.id}`;
1046
- writeGateLog(deps, batchId, gateInconclusive.id, memberIssue, gateInconclusive.outputTail);
1047
- const excerpt = gateDetailExcerpt(gateInconclusive.outputTail, gateInconclusive.reason);
1078
+ const inconclusive = gateInconclusive ?? unevidencedFailure;
1079
+ if (inconclusive) {
1080
+ const reason = `gate-inconclusive:${inconclusive.id}`;
1081
+ writeGateLog(deps, batchId, inconclusive.id, memberIssue, inconclusive.outputTail);
1082
+ const excerpt = gateDetailExcerpt(inconclusive.outputTail, inconclusive.reason);
1083
+ // #594 AC3: say WHICH of the two block-the-batch branches fired. Both land
1084
+ // on `gate-inconclusive:<cap>`, and a later run must not have to re-derive
1085
+ // "the capability was broken" from "the capability reported a failure it
1086
+ // could not evidence" — the operator's next action differs.
1048
1087
  journalEvent(deps, 'gate-inconclusive', unit(batchId), {
1049
1088
  issue: memberIssue,
1050
1089
  reason,
1051
- detail: excerpt
1052
- ? `cap run ${gateInconclusive.id} reported ${gateInconclusive.outcome} after member review done: ${excerpt}`
1053
- : `cap run ${gateInconclusive.id} reported ${gateInconclusive.outcome} after member review done`,
1090
+ detail: withExcerpt(`cap run ${inconclusive.id} ${describeInconclusive(inconclusive)} after member review done`, excerpt),
1054
1091
  });
1055
1092
  deps.store.withLock((s) => ({ state: releaseSlot(s, batchId, now), result: undefined }));
1056
1093
  const stateNow = deps.store.load();
@@ -1100,8 +1137,10 @@ function completeMemberGate(deps, config, dispatch, batchId, batch, memberIssue,
1100
1137
  *
1101
1138
  * - still `automation-broken`/`capability-unavailable` → stays `blocked`,
1102
1139
  * no state change (the capability still isn't fixed).
1103
- * - `task-failed` → the member really is broken; evict via the same rail
1104
- * the live gate uses.
1140
+ * - `task-failed` with recognizable failing-test evidence (#594,
1141
+ * `hasFailingTestEvidence`) → the member really is broken; evict via the
1142
+ * same rail the live gate uses. `task-failed` with no evidence stays
1143
+ * `blocked`, same as the still-inconclusive case above.
1105
1144
  * - `ok` → the member really was fine; complete it via the same rail the
1106
1145
  * live gate uses.
1107
1146
  *
@@ -1140,13 +1179,31 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1140
1179
  journalEvent(deps, 'gate-inconclusive', unit(batchId), {
1141
1180
  issue: memberIssue,
1142
1181
  reason: `gate-inconclusive:${capabilityId}`,
1143
- detail: excerpt
1144
- ? `sched resume --batch: cap run ${capabilityId} still reports ${recheck.outcome} on recheck: ${excerpt}`
1145
- : `sched resume --batch: cap run ${capabilityId} still reports ${recheck.outcome} on recheck`,
1182
+ detail: withExcerpt(`sched resume --batch: cap run ${capabilityId} still reports ${recheck.outcome} on recheck`, excerpt),
1146
1183
  });
1147
1184
  return {
1148
1185
  outcome: 'still-blocked',
1149
1186
  capability: capabilityId,
1187
+ blockedBy: 'inconclusive',
1188
+ detail: excerpt,
1189
+ };
1190
+ }
1191
+ // A recheck can still come back `task-failed` with no evidence behind it
1192
+ // (#594) — the same routing the live gate applies, so a resumed batch
1193
+ // never evicts a member for a failure `hasFailingTestEvidence` cannot
1194
+ // confirm.
1195
+ if (recheck.outcome === 'task-failed' &&
1196
+ !(0, attribution_1.hasEarnedFailureEvidence)(capabilityId, recheck.outputTail)) {
1197
+ writeGateLog(deps, batchId, capabilityId, memberIssue, recheck.outputTail);
1198
+ journalEvent(deps, 'gate-inconclusive', unit(batchId), {
1199
+ issue: memberIssue,
1200
+ reason: `gate-inconclusive:${capabilityId}`,
1201
+ detail: withExcerpt(`sched resume --batch: cap run ${capabilityId} still reports task-failed with no failing-test evidence on recheck`, excerpt),
1202
+ });
1203
+ return {
1204
+ outcome: 'still-blocked',
1205
+ capability: capabilityId,
1206
+ blockedBy: 'unevidenced-failure',
1150
1207
  detail: excerpt,
1151
1208
  };
1152
1209
  }
@@ -1160,9 +1217,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1160
1217
  journalEvent(deps, 'unit-failed', unit(batchId), {
1161
1218
  issue: memberIssue,
1162
1219
  reason,
1163
- detail: excerpt
1164
- ? `sched resume --batch: cap run ${capabilityId} reported task-failed on recheck: ${excerpt}`
1165
- : `sched resume --batch: cap run ${capabilityId} reported task-failed on recheck`,
1220
+ detail: withExcerpt(`sched resume --batch: cap run ${capabilityId} reported task-failed on recheck`, excerpt),
1166
1221
  });
1167
1222
  evictMemberAndContinue(deps, config, dispatch, batchId, batch, memberIssue, reason, now, result);
1168
1223
  return { outcome: 'evicted', capability: capabilityId, detail: excerpt };
@@ -1319,20 +1374,33 @@ function reconcileMemberSlot(deps, config, dispatch, batchId, slot, now, result)
1319
1374
  }
1320
1375
  /**
1321
1376
  * Requeue a member full-cycle, record the eviction, and dissolve if this tips
1322
- * the batch past the ⅓ threshold (RFC F.1/F.8) — WITHOUT going through
1323
- * `recovery.ts`'s `evictMembers` (which needs `attributing`/`evicting` status
1324
- * and a commit range to revert; a member evicted here has neither). Returns
1325
- * whether the batch dissolved.
1377
+ * the batch past its `dissolve_policy` threshold (#563; RFC F.1/F.8) — WITHOUT
1378
+ * going through `recovery.ts`'s `evictMembers` (which needs
1379
+ * `attributing`/`evicting` status and a commit range to revert; a member
1380
+ * evicted here has neither). Returns whether the batch dissolved.
1381
+ *
1382
+ * A member already in `evictions[]` is a NO-OP here, not merely a skipped
1383
+ * record (#595 AC1): the requeue must not run a second time either. It would
1384
+ * overwrite the first eviction's `failure_evidence` — leaving `evictions[]`
1385
+ * and the queue entry disagreeing about why the member was evicted — and, if
1386
+ * the member has since been re-dispatched full-cycle, would take the
1387
+ * `executing → evicted → requeued` rail and kill that live run.
1326
1388
  */
1327
1389
  function evictMemberDirectly(deps, config, batchId, memberIssue, reason, now) {
1328
1390
  const dissolvePolicy = (0, types_1.resolveDissolvePolicy)(config.dissolve_policy);
1329
1391
  // Pass 1 (pure — requeue + record the eviction): safe to run entirely
1330
1392
  // inside the lock, unlike `dissolveBatch` below, which shells out
1331
1393
  // (`deps.exec`/`postMilestone`) and so must NOT hold the lock while it runs.
1332
- const triggered = deps.store.withLock((s) => {
1394
+ const { triggered, duplicate } = deps.store.withLock((s) => {
1333
1395
  const b = (0, state_1.findBatch)(s, batchId);
1334
1396
  if (!b)
1335
- return { state: s, result: false };
1397
+ return { state: s, result: { triggered: false, duplicate: false } };
1398
+ if (b.evictions.some((e) => e.issue === memberIssue)) {
1399
+ return {
1400
+ state: s,
1401
+ result: { triggered: (0, recovery_1.checkDissolveTrigger)(b, dissolvePolicy), duplicate: true },
1402
+ };
1403
+ }
1336
1404
  const evidence = {
1337
1405
  batch: batchId,
1338
1406
  reason,
@@ -1343,25 +1411,34 @@ function evictMemberDirectly(deps, config, batchId, memberIssue, reason, now) {
1343
1411
  };
1344
1412
  const requeueResult = (0, state_1.requeueMember)(s, memberIssue, { mode: 'full', batch: null }, reason, now, { failure_evidence: evidence });
1345
1413
  let next = requeueResult.state;
1346
- next = (0, state_1.patchBatch)(next, batchId, {
1347
- evictions: [
1348
- ...b.evictions,
1349
- {
1350
- issue: memberIssue,
1351
- reason,
1352
- attribution: 'none',
1353
- reverted_commits: [],
1354
- group: [],
1355
- at: now.toISOString(),
1356
- },
1357
- ],
1358
- }, now);
1414
+ // `appendEvictions` stays the state-level backstop even though the
1415
+ // duplicate is already short-circuited above — it is the one append path.
1416
+ const { evictions, duplicate: duplicateRecords } = (0, state_1.appendEvictions)(b, [
1417
+ {
1418
+ issue: memberIssue,
1419
+ reason,
1420
+ attribution: 'none',
1421
+ reverted_commits: [],
1422
+ group: [],
1423
+ at: now.toISOString(),
1424
+ },
1425
+ ]);
1426
+ next = (0, state_1.patchBatch)(next, batchId, { evictions }, now);
1359
1427
  const updated = (0, state_1.findBatch)(next, batchId);
1360
1428
  return {
1361
1429
  state: next,
1362
- result: updated !== undefined && (0, recovery_1.checkDissolveTrigger)(updated, dissolvePolicy),
1430
+ result: {
1431
+ triggered: updated !== undefined && (0, recovery_1.checkDissolveTrigger)(updated, dissolvePolicy),
1432
+ duplicate: duplicateRecords.length > 0,
1433
+ },
1363
1434
  };
1364
1435
  });
1436
+ if (duplicate) {
1437
+ journalEvent(deps, 'eviction-duplicate', unit(batchId), {
1438
+ issue: memberIssue,
1439
+ detail: (0, state_1.duplicateEvictionDetail)(memberIssue, reason),
1440
+ });
1441
+ }
1365
1442
  if (!triggered)
1366
1443
  return false;
1367
1444
  // Pass 2 (outside the lock — dissolveBatch shells out): re-load fresh