@ai-dossier/sched 0.14.0 → 0.16.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 +52 -21
- package/dist/attribution.d.ts +10 -0
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +19 -0
- package/dist/attribution.js.map +1 -1
- package/dist/batch-dispatch.d.ts +33 -14
- package/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +194 -20
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/engine.d.ts +6 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/persist.js +6 -0
- package/dist/persist.js.map +1 -1
- package/dist/recovery.d.ts +35 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +42 -1
- package/dist/recovery.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +13 -1
- package/dist/state.js.map +1 -1
- package/dist/teardown.d.ts +12 -0
- package/dist/teardown.d.ts.map +1 -1
- package/dist/teardown.js +8 -5
- package/dist/teardown.js.map +1 -1
- package/dist/types.d.ts +40 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ invokes an LLM** — it spawns the agent process the operator configured and rec
|
|
|
13
13
|
the durable record (`ai-dossier runstate` / `gh` / `git`) that the spawned run leaves
|
|
14
14
|
behind.
|
|
15
15
|
|
|
16
|
-
Design: RFC-0001 *Batch Cycles*
|
|
17
|
-
|
|
16
|
+
Design: RFC-0001 *Batch Cycles* ([`rfcs/0001-batch-cycles.md`](../../rfcs/0001-batch-cycles.md))
|
|
17
|
+
§B/C.1/D — the §D state machines are frozen verbatim into the types below.
|
|
18
18
|
This package is the deterministic replacement for fleet-cycle's LLM-prose supervision,
|
|
19
19
|
whose named failure — slots sitting idle after a subagent finished — is a scheduling bug
|
|
20
20
|
this state machine makes impossible to forget.
|
|
@@ -158,6 +158,20 @@ as the shorthand and are the fallback for any field a `tiers` entry leaves unset
|
|
|
158
158
|
1.3.0 config with no `tiers` at all resolves identically to before — there is no on-disk
|
|
159
159
|
migration, only resolution-time fallback in `resolveDispatch`.
|
|
160
160
|
|
|
161
|
+
Config schema moves to 1.6.0 (#562): `dispatch` gains `suite_command` — an explicit argv
|
|
162
|
+
override for the aggregate batch-suite command, the middle tier of the new resolution
|
|
163
|
+
order (`cap run test.full` manifest → `dispatch.suite_command` → a repo-detected safe
|
|
164
|
+
default that never forwards extra flags through an unrecognized wrapper script). Also new:
|
|
165
|
+
the `blocked` `BatchStatus` and `batch-blocked` journal event — an unreadable suite report
|
|
166
|
+
blocks the batch (worktree and every member commit preserved) instead of dissolving it,
|
|
167
|
+
distinct from `dissolving`'s "a red suite named no offender." `blocked` is a new
|
|
168
|
+
persisted-field *value*, not a new field, so no `state.json` schema-version bump or
|
|
169
|
+
backfill is needed on load — but it is NOT downgrade-safe: an older build's
|
|
170
|
+
`BATCH_STATUSES` (derived from its own `BATCH_TRANSITIONS`) will reject a `state.json`
|
|
171
|
+
containing a `blocked` batch with "unknown batch status", bricking that project's whole
|
|
172
|
+
`SchedStore.load()`. Resume or abandon any blocked batch before downgrading past this
|
|
173
|
+
version.
|
|
174
|
+
|
|
161
175
|
Two engine-safety policies were explicit product decisions on #464:
|
|
162
176
|
|
|
163
177
|
- **Pid identity is hybrid-verified (decision 1, option C).** Every spawn records the
|
|
@@ -235,6 +249,8 @@ against real scratch repos.
|
|
|
235
249
|
validating → attributing → fixing (ONE bounded attempt) → validating
|
|
236
250
|
→ evicting (revert the member's commits) → validating
|
|
237
251
|
> ⅓ of members evicted, or a revert conflict → dissolving → members requeued
|
|
252
|
+
suite report unreadable, after the fallback retry when one applied
|
|
253
|
+
→ blocked → validating (nothing requeued/reverted; #562)
|
|
238
254
|
awaiting-merge (CONFLICTING | auto-merge-blocked)
|
|
239
255
|
→ rebasing → re-validating → shipping
|
|
240
256
|
→ (2nd occurrence) dissolving into two half-batches
|
|
@@ -283,11 +299,12 @@ awaiting-merge (CONFLICTING | auto-merge-blocked)
|
|
|
283
299
|
with no `anchor` or no `run_id` cannot post — the CLI requires both — so the milestone
|
|
284
300
|
it could not post is journaled in full instead of vanishing.
|
|
285
301
|
|
|
286
|
-
|
|
302
|
+
Eleven journal events carry the detail: `suite-failed`, `attributed`, `fix-dispatched`,
|
|
287
303
|
`fix-resolved`, `member-evicted`, `revert-conflict`, `batch-rebased`, `batch-dissolved`,
|
|
288
|
-
`batch-
|
|
289
|
-
|
|
290
|
-
command that produced one
|
|
304
|
+
`batch-blocked` (#562 — the suite report was unreadable), `batch-split` and
|
|
305
|
+
`milestone-post-failed`, plus `git-failed` for any git command that returned non-zero (the
|
|
306
|
+
injected `ExecFn` collapses every git failure into `null`, so the command that produced one
|
|
307
|
+
is always recorded).
|
|
291
308
|
|
|
292
309
|
Schema 1.3.0 carries the new state: `BatchEntry` gains `anchor`, `branch`, `run_id`,
|
|
293
310
|
`eviction_groups`, `evictions`, `fix_attempts` and `rebase_attempts`; `QueueEntry` gains
|
|
@@ -344,11 +361,18 @@ ready → executing(member i/N) ⟲ → validating → reviewing → shipping
|
|
|
344
361
|
→ awaiting-merge → merged → deployed → reported → done
|
|
345
362
|
failure rails: executing → dissolving (a member self-reports blocked)
|
|
346
363
|
validating → attributing → (fixing | evicting) → validating → dissolving
|
|
364
|
+
validating → blocked (suite report unreadable, #562) → validating
|
|
347
365
|
```
|
|
348
366
|
|
|
349
367
|
- **One shared worktree/branch per batch**, claimed once by a deterministic (no LLM)
|
|
350
|
-
`batch-setup` step
|
|
351
|
-
|
|
368
|
+
`batch-setup` step, named `batch/<id>-<date>`, plus a fresh `ai-dossier runstate mint`
|
|
369
|
+
against the anchor issue. Tries a pool claim first (`npx worktree-pool claim` —
|
|
370
|
+
already warm by construction, `BatchEntry.pool_claimed`); on the cold `git branch`/
|
|
371
|
+
`push`/`worktree add` path, batch-setup warms the worktree itself before returning
|
|
372
|
+
(#561) — `cap run worktree.prepare` when the repo's manifest declares it, else
|
|
373
|
+
package-manager-detected install/build (`@ai-dossier/worktree-pool`'s command
|
|
374
|
+
resolution — respects the repo's `.worktree-pool.json` `project_subdir`/
|
|
375
|
+
`warm_commands` even in repos that never use the pool for anything else).
|
|
352
376
|
- **Members run serially, one fresh `slot-cycle` agent at a time**, in the shared
|
|
353
377
|
worktree. A member's completion signal is `phase=review status=done mode=slot` on its
|
|
354
378
|
OWN issue (`slot-cycle` posts no phase of its own past `review` — ship is batch-owned);
|
|
@@ -373,23 +397,26 @@ failure rails: executing → dissolving (a member self-reports blocked)
|
|
|
373
397
|
`report-issue`'s batch variant.
|
|
374
398
|
- **Scope cuts, recorded rather than discovered later:** no `git bisect` stage for an
|
|
375
399
|
ambiguous aggregate failure (an unattributable red suite dissolves instead); no
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
treated as blocked, not redispatched stronger).
|
|
400
|
+
per-phase stall/escalation ladder for batch sub-agents (a dead-without-verification
|
|
401
|
+
agent is treated as blocked, not redispatched stronger).
|
|
379
402
|
|
|
380
403
|
Schema 1.7.0: `BatchEntry` gains `worktree` (absolute path of the shared batch worktree,
|
|
381
404
|
null until batch-setup lands), `ranges` (`MemberRange[]` — each member's commit range,
|
|
382
405
|
recomputed after every member completes) and `pr` (the batch PR parked on auto-merge,
|
|
383
406
|
persisted so a restart mid-watch still knows what to poll). 1.6.0 states migrate on load:
|
|
384
407
|
no batch was ever dispatched under them, so `null`/`[]`/`null` is the exact backfill, not
|
|
385
|
-
a guess.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
`
|
|
392
|
-
`
|
|
408
|
+
a guess. `BatchEntry` also gains `pool_claimed` (#561) — pre-#561 states carry no such
|
|
409
|
+
key at all, and backfill it to `false` on load (no batch was ever pool-claimed before
|
|
410
|
+
batch-setup had pool integration). Config schema moves to 1.3.0: `dispatch` gains
|
|
411
|
+
`member_prompt`, `batch_tail_prompt` and `batch_report_prompt` (the three new agent
|
|
412
|
+
prompt templates).
|
|
413
|
+
|
|
414
|
+
New journal events: `batch-setup-done`, `batch-setup-failed`, `member-advanced`,
|
|
415
|
+
`batch-warmup-done`, `batch-warmup-failed` (#561 — the cold-path warm step only; a pool
|
|
416
|
+
claim emits neither). Member/tail/report/fix-agent spawn, progress, completion and park
|
|
417
|
+
events reuse the existing unit-generic names (`assigned`/`spawned`/`unit-failed`/
|
|
418
|
+
`external-advance`/`pr-parked`/`merge-accepted`/`report-dispatched`/`teardown-done`/
|
|
419
|
+
`teardown-failed`) with `unit = batch:<id>`.
|
|
393
420
|
|
|
394
421
|
## API surface
|
|
395
422
|
|
|
@@ -447,6 +474,8 @@ import {
|
|
|
447
474
|
TEARDOWN_TIMEOUT_MS, // teardown subprocess timeout (120 s)
|
|
448
475
|
attributeByOverlap, // #472 pure stage-1 attribution: failing tests → members
|
|
449
476
|
parseVitestJson, // vitest --reporter=json → failing tests
|
|
477
|
+
isReadableVitestReport, // #562: a parseable { testResults: [...] } document exists —
|
|
478
|
+
// distinct from "zero failures"
|
|
450
479
|
parseBoundaryCommits, // git log → issue-boundary commits via the (#N) trailer
|
|
451
480
|
memberRanges, // boundary commits → each member's commit list
|
|
452
481
|
runAttributionBisect, // stage-2: real git bisect over the failing tests only
|
|
@@ -456,6 +485,8 @@ import {
|
|
|
456
485
|
evictMembers, // revert + requeue with evidence + suite re-run + dissolve check
|
|
457
486
|
checkDissolveTrigger, // pure: > ⅓ of members evicted
|
|
458
487
|
dissolveBatch, // full or halved dissolve; preserves everything green
|
|
488
|
+
blockBatch, // #562: unreadable suite report → blocked; no requeue, no revert
|
|
489
|
+
type BlockOptions, // { reason, milestonePhase? } for blockBatch
|
|
459
490
|
handlePrConflict, // rebase + re-ship once, then dissolve into halves
|
|
460
491
|
createExecMilestonePoster, // batch milestones via `ai-dossier runstate post`
|
|
461
492
|
expandEvictionGroups, // members that must revert together (§E.4 eviction groups)
|
|
@@ -519,8 +550,8 @@ telemetry" below.
|
|
|
519
550
|
├── state.json # hot operational truth — atomic tmp+fsync+rename writes;
|
|
520
551
|
├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms,
|
|
521
552
|
│ # pr_poll_interval_ms, dispatch (incl. report_prompt,
|
|
522
|
-
│ # phase_stall_timeout_ms, fence_takeover_timeout_ms, tiers — #527
|
|
523
|
-
│ # auto_upgrade — #537
|
|
553
|
+
│ # phase_stall_timeout_ms, fence_takeover_timeout_ms, tiers — #527,
|
|
554
|
+
│ # suite_command — #562), auto_upgrade — #537
|
|
524
555
|
├── events.jsonl # append-only event journal (the operator's flight recorder)
|
|
525
556
|
├── runs/ # per-unit agent output logs (issue-<n>.log)
|
|
526
557
|
└── .sched-lock/ # cross-process directory mutex (pid; stolen from dead holders)
|
package/dist/attribution.d.ts
CHANGED
|
@@ -65,6 +65,16 @@ export declare function offendersOf(attributed: ReadonlyMap<number, unknown>): n
|
|
|
65
65
|
* "no attributable tests", which routes to bisect, never to a guess.
|
|
66
66
|
*/
|
|
67
67
|
export declare function parseVitestJson(stdout: string | null): FailingTest[];
|
|
68
|
+
/**
|
|
69
|
+
* Whether `stdout` contains a parseable vitest JSON report (a `{ testResults:
|
|
70
|
+
* [...] }` document) — independent of whether any test in it failed (#562).
|
|
71
|
+
* `parseVitestJson` returns `[]` both for "found a report, zero failures" and
|
|
72
|
+
* "found no report at all" (an empty string, a wrapper script's plain-text
|
|
73
|
+
* abort message, a make error): those two are NOT the same thing to a caller
|
|
74
|
+
* deciding whether to trust a red exit code's failing-test list, or a green
|
|
75
|
+
* exit code that happens to carry no parseable body.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isReadableVitestReport(stdout: string | null): boolean;
|
|
68
78
|
/** One commit on the batch branch, with the member it belongs to. */
|
|
69
79
|
export interface BoundaryCommit {
|
|
70
80
|
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;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":"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"}
|
package/dist/attribution.js
CHANGED
|
@@ -22,6 +22,7 @@ exports.failingTest = failingTest;
|
|
|
22
22
|
exports.attributeByOverlap = attributeByOverlap;
|
|
23
23
|
exports.offendersOf = offendersOf;
|
|
24
24
|
exports.parseVitestJson = parseVitestJson;
|
|
25
|
+
exports.isReadableVitestReport = isReadableVitestReport;
|
|
25
26
|
exports.parseBoundaryCommits = parseBoundaryCommits;
|
|
26
27
|
exports.memberRanges = memberRanges;
|
|
27
28
|
exports.memberOfCommit = memberOfCommit;
|
|
@@ -138,6 +139,24 @@ function parseVitestJson(stdout) {
|
|
|
138
139
|
}
|
|
139
140
|
return out;
|
|
140
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Whether `stdout` contains a parseable vitest JSON report (a `{ testResults:
|
|
144
|
+
* [...] }` document) — independent of whether any test in it failed (#562).
|
|
145
|
+
* `parseVitestJson` returns `[]` both for "found a report, zero failures" and
|
|
146
|
+
* "found no report at all" (an empty string, a wrapper script's plain-text
|
|
147
|
+
* abort message, a make error): those two are NOT the same thing to a caller
|
|
148
|
+
* deciding whether to trust a red exit code's failing-test list, or a green
|
|
149
|
+
* exit code that happens to carry no parseable body.
|
|
150
|
+
*/
|
|
151
|
+
function isReadableVitestReport(stdout) {
|
|
152
|
+
if (stdout === null)
|
|
153
|
+
return false;
|
|
154
|
+
const parsed = extractJsonObject(stdout);
|
|
155
|
+
if (parsed === null)
|
|
156
|
+
return false;
|
|
157
|
+
const results = parsed.testResults;
|
|
158
|
+
return Array.isArray(results);
|
|
159
|
+
}
|
|
141
160
|
/** The first balanced `{...}` document in `text`, parsed; null when there is none. */
|
|
142
161
|
function extractJsonObject(text) {
|
|
143
162
|
const start = text.indexOf('{');
|
package/dist/attribution.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAuCH,kCAEC;AA0CD,gDA2BC;AAGD,kCAEC;AAWD,0CA0BC;AAqED,oDAaC;AA6BD,oCAgBC;AAWD,wCASC;
|
|
1
|
+
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
|
package/dist/batch-dispatch.d.ts
CHANGED
|
@@ -47,12 +47,18 @@
|
|
|
47
47
|
* an ambiguous aggregate failure (bisect needs a per-project "run only these
|
|
48
48
|
* tests" command this module has no generic way to construct) — an
|
|
49
49
|
* unattributable red aggregate suite dissolves the batch rather than
|
|
50
|
-
* bisecting, which `attributing → dissolving` already models. No
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
50
|
+
* bisecting, which `attributing → dissolving` already models. No per-phase
|
|
51
|
+
* stall/escalation ladder for batch sub-agents — a dead-without-verification
|
|
52
|
+
* agent is treated as blocked and evicted/reported rather than redispatched
|
|
53
|
+
* stronger. Both are documented follow-ups, not gaps discovered later.
|
|
54
|
+
*
|
|
55
|
+
* `runBatchSetup` (#561) tries a pool claim first (already warm, mirroring
|
|
56
|
+
* `teardown.ts`'s `poolReturn` — same `npx`-through-`deps.exec` pattern, never
|
|
57
|
+
* a direct in-process `claim()` import, which resolves its git root from
|
|
58
|
+
* `process.cwd()` and would break this module's `deps.repoDir` testability
|
|
59
|
+
* contract); on the cold `git worktree add` path it warms the worktree itself
|
|
60
|
+
* before returning, so a member's first command is never the one that
|
|
61
|
+
* discovers `node_modules` is missing (`env-cold`, `docs/agent-traps.md`).
|
|
56
62
|
*/
|
|
57
63
|
import { type ResolvedDispatch, type SpawnDeps } from './dispatch';
|
|
58
64
|
import { type GroundTruth } from './groundtruth';
|
|
@@ -81,17 +87,30 @@ export interface BatchDispatchDeps {
|
|
|
81
87
|
repoDir: string;
|
|
82
88
|
/** Exec for batch git/milestone-CLI operations (never throws — the `ExecFn` contract). */
|
|
83
89
|
exec: ExecFn;
|
|
90
|
+
/**
|
|
91
|
+
* Exec for the cold-path warm-up install/build (#561), on its own budget —
|
|
92
|
+
* a real `npm ci` + build routinely exceeds the git-op timeout `exec` is
|
|
93
|
+
* tuned for. Falls back to `exec` when not supplied (existing callers/tests
|
|
94
|
+
* are unaffected; production wiring should still give this its own longer
|
|
95
|
+
* timeout, e.g. `@ai-dossier/worktree-pool`'s `WARM_COMMAND_TIMEOUT_MS`).
|
|
96
|
+
*/
|
|
97
|
+
warmExec?: ExecFn;
|
|
84
98
|
/** Runs the aggregate suite inside a batch worktree; batches never leave `validating` without one. */
|
|
85
99
|
runSuite: (worktree: string) => SuiteResult;
|
|
86
100
|
/**
|
|
87
|
-
* Runs one `ai-dossier cap run <capabilityId>` in a batch worktree
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* `capability-unavailable`, the gate
|
|
92
|
-
* `slot-cycle` run already attempted this
|
|
93
|
-
*
|
|
94
|
-
*
|
|
101
|
+
* Runs one `ai-dossier cap run <capabilityId>` in a batch worktree. Two call
|
|
102
|
+
* sites, different degrade contracts:
|
|
103
|
+
* - the per-member incremental gate (#523 AC2 — "typecheck + focused tests
|
|
104
|
+
* via `cap run test.focused` when available"): degrade-not-crash — without
|
|
105
|
+
* this hook, or on `automation-broken`/`capability-unavailable`, the gate
|
|
106
|
+
* is skipped (the member's own `slot-cycle` run already attempted this
|
|
107
|
+
* fast path before ever posting `review done`, so a repo with no manifest
|
|
108
|
+
* loses nothing but the engine's independent re-check).
|
|
109
|
+
* - batch-setup's `worktree.prepare` warm step (#561, `warmColdBatchWorktree`):
|
|
110
|
+
* `undefined`/`capability-unavailable`/`automation-broken` fall through to
|
|
111
|
+
* package-manager detection, but a declared-and-`task-failed` capability
|
|
112
|
+
* hard-fails the whole batch setup — a repo that owns its warm-up should
|
|
113
|
+
* never be silently second-guessed by a fallback underneath it.
|
|
95
114
|
*/
|
|
96
115
|
runCapability?: (worktree: string, capabilityId: string) => CapOutcome;
|
|
97
116
|
fsExists?: FsExists;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAiBH,OAAO,EAKL,KAAK,gBAAgB,EAErB,KAAK,SAAS,EAEf,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;AACxC,OAAO,EAUL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,KAAK,QAAQ,EAA2D,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAKV,WAAW,EAIZ,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,aAAa,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;AAE/F,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;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,UAAU,CAAC;IACvE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;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;AA67CD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsEjB"}
|
package/dist/batch-dispatch.js
CHANGED
|
@@ -48,12 +48,18 @@
|
|
|
48
48
|
* an ambiguous aggregate failure (bisect needs a per-project "run only these
|
|
49
49
|
* tests" command this module has no generic way to construct) — an
|
|
50
50
|
* unattributable red aggregate suite dissolves the batch rather than
|
|
51
|
-
* bisecting, which `attributing → dissolving` already models. No
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* bisecting, which `attributing → dissolving` already models. No per-phase
|
|
52
|
+
* stall/escalation ladder for batch sub-agents — a dead-without-verification
|
|
53
|
+
* agent is treated as blocked and evicted/reported rather than redispatched
|
|
54
|
+
* stronger. Both are documented follow-ups, not gaps discovered later.
|
|
55
|
+
*
|
|
56
|
+
* `runBatchSetup` (#561) tries a pool claim first (already warm, mirroring
|
|
57
|
+
* `teardown.ts`'s `poolReturn` — same `npx`-through-`deps.exec` pattern, never
|
|
58
|
+
* a direct in-process `claim()` import, which resolves its git root from
|
|
59
|
+
* `process.cwd()` and would break this module's `deps.repoDir` testability
|
|
60
|
+
* contract); on the cold `git worktree add` path it warms the worktree itself
|
|
61
|
+
* before returning, so a member's first command is never the one that
|
|
62
|
+
* discovers `node_modules` is missing (`env-cold`, `docs/agent-traps.md`).
|
|
57
63
|
*/
|
|
58
64
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
59
65
|
if (k2 === undefined) k2 = k;
|
|
@@ -90,7 +96,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
90
96
|
})();
|
|
91
97
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
92
98
|
exports.runBatchTick = runBatchTick;
|
|
99
|
+
const fs = __importStar(require("node:fs"));
|
|
93
100
|
const path = __importStar(require("node:path"));
|
|
101
|
+
const worktree_pool_1 = require("@ai-dossier/worktree-pool");
|
|
94
102
|
const attribution_1 = require("./attribution");
|
|
95
103
|
const dispatch_1 = require("./dispatch");
|
|
96
104
|
const groundtruth_1 = require("./groundtruth");
|
|
@@ -177,7 +185,9 @@ function safeSuite(deps, batchId, worktree) {
|
|
|
177
185
|
catch (err) {
|
|
178
186
|
const detail = `suite runner threw: ${err.message}`;
|
|
179
187
|
journalEvent(deps, 'suite-failed', unit(batchId), { detail });
|
|
180
|
-
|
|
188
|
+
// A throw is exactly "no trustworthy report" (#562) — must not default to
|
|
189
|
+
// `readable: true` and look like a parseable report naming zero failures.
|
|
190
|
+
return { ok: false, failing: [], readable: false, detail };
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
193
|
function recoveryDeps(deps, batch, now) {
|
|
@@ -228,11 +238,93 @@ function releaseSlot(state, batchId, now) {
|
|
|
228
238
|
}
|
|
229
239
|
// --- Batch setup (ready → executing, member 1) ---
|
|
230
240
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
241
|
+
* A pool claim already returns a warm worktree (deps installed, built), so
|
|
242
|
+
* only the cold `git worktree add` path needs its own warm step (#561).
|
|
243
|
+
*
|
|
244
|
+
* Tries `worktree.prepare` via the optional `runCapability` hook first (a
|
|
245
|
+
* repo that declares it in its manifest knows its own warm-up best) — a
|
|
246
|
+
* declared-and-`task-failed` capability hard-fails the whole batch setup
|
|
247
|
+
* (deliberately NOT degrade-not-crash, unlike every other `runCapability`
|
|
248
|
+
* call site in this file: a repo that owns `worktree.prepare` should never
|
|
249
|
+
* be silently second-guessed by falling back underneath it). `undefined`,
|
|
250
|
+
* `capability-unavailable` (no such id declared) AND `automation-broken`
|
|
251
|
+
* (broken manifest, missing tool, timeout — "do not trust the machinery",
|
|
252
|
+
* not "the task failed") all fall through to the generic path below.
|
|
253
|
+
*
|
|
254
|
+
* The generic path reuses `@ai-dossier/worktree-pool`'s package-manager
|
|
255
|
+
* detection, guarded on `package.json` actually being present — `npm
|
|
256
|
+
* install` (and the pnpm/yarn/bun equivalents) hard-fail with no
|
|
257
|
+
* `package.json` at all, so an unguarded call would turn "nothing to warm"
|
|
258
|
+
* into a false failure for every non-Node repo. The guard is bypassed when
|
|
259
|
+
* `.worktree-pool.json` declares explicit `warm_commands` — the escape hatch
|
|
260
|
+
* for a repo whose warm-up isn't `npm install`-shaped at all.
|
|
261
|
+
*
|
|
262
|
+
* Warm commands run on `deps.warmExec ?? deps.exec` — a separate, longer
|
|
263
|
+
* budget than the git/milestone calls `deps.exec` is tuned for (a cold
|
|
264
|
+
* install+build routinely exceeds a git-op timeout; see `BatchDispatchDeps`).
|
|
265
|
+
*
|
|
266
|
+
* Journals `batch-warmup-done`/`batch-warmup-failed` with the elapsed time
|
|
267
|
+
* appended to `detail` either way (satisfying AC1 even on the no-op branch —
|
|
268
|
+
* `JournalEvent` has no dedicated `duration_ms` field).
|
|
269
|
+
*/
|
|
270
|
+
function warmColdBatchWorktree(deps, batch, worktree, now,
|
|
271
|
+
/** Prefixed onto the journaled detail — e.g. `pool-claim-invalid:` when the cold path was taken because a pool claim returned unusable output, rather than the ordinary "no warm spares" case. */
|
|
272
|
+
poolNote = '') {
|
|
273
|
+
const warmStart = deps.now();
|
|
274
|
+
const elapsedMs = () => deps.now().getTime() - warmStart.getTime();
|
|
275
|
+
const fail = (tag) => {
|
|
276
|
+
deps.journal.append((0, journal_1.unitEvent)('batch-warmup-failed', unit(batch.id), {
|
|
277
|
+
detail: `${poolNote}${tag} ${elapsedMs()}ms`,
|
|
278
|
+
}), now);
|
|
279
|
+
return { ok: false, reason: `warmup-failed:${tag}` };
|
|
280
|
+
};
|
|
281
|
+
const done = (tag) => {
|
|
282
|
+
deps.journal.append((0, journal_1.unitEvent)('batch-warmup-done', unit(batch.id), {
|
|
283
|
+
detail: `${poolNote}${tag} ${elapsedMs()}ms`,
|
|
284
|
+
}), now);
|
|
285
|
+
return { ok: true };
|
|
286
|
+
};
|
|
287
|
+
const capOutcome = deps.runCapability?.(worktree, 'worktree.prepare');
|
|
288
|
+
if (capOutcome === 'ok')
|
|
289
|
+
return done('cap:worktree.prepare');
|
|
290
|
+
if (capOutcome === 'task-failed')
|
|
291
|
+
return fail('cap:worktree.prepare:task-failed');
|
|
292
|
+
// `undefined` (no hook injected) / `capability-unavailable` (not declared)
|
|
293
|
+
// / `automation-broken` (declared but the machinery itself is untrustworthy)
|
|
294
|
+
// all fall through to the generic package-manager path below.
|
|
295
|
+
const fsExists = deps.fsExists ?? ((p) => fs.existsSync(p));
|
|
296
|
+
const cfg = (0, worktree_pool_1.readPoolFileConfig)(deps.repoDir);
|
|
297
|
+
const projectDir = (0, worktree_pool_1.resolveProjectDir)(worktree, cfg.project_subdir);
|
|
298
|
+
// `.worktree-pool.json`'s `project_subdir` is repo config, not attacker
|
|
299
|
+
// input, but a `../`-shaped value would otherwise run install/build
|
|
300
|
+
// OUTSIDE the batch worktree entirely — skip rather than warm the wrong
|
|
301
|
+
// directory (or fail the whole batch over a misconfigured pool file).
|
|
302
|
+
if (projectDir !== worktree && !projectDir.startsWith(worktree + path.sep)) {
|
|
303
|
+
return done('skipped:project-dir-outside-worktree');
|
|
304
|
+
}
|
|
305
|
+
const hasExplicitWarmCommands = (cfg.warm_commands?.length ?? 0) > 0;
|
|
306
|
+
if (!hasExplicitWarmCommands && !fsExists(path.join(projectDir, 'package.json'))) {
|
|
307
|
+
return done('skipped:no-package-json');
|
|
308
|
+
}
|
|
309
|
+
const commands = (0, worktree_pool_1.resolveWarmCommands)(projectDir, cfg);
|
|
310
|
+
for (const [i, cmd] of commands.entries()) {
|
|
311
|
+
const [bin, ...args] = cmd;
|
|
312
|
+
if (bin === undefined)
|
|
313
|
+
return fail(`pm:${i + 1}/${commands.length}:empty-command`);
|
|
314
|
+
if ((deps.warmExec ?? deps.exec)(bin, args, projectDir) === null) {
|
|
315
|
+
return fail(`pm:${i + 1}/${commands.length}:${bin}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const pm = commands[0]?.[0] ?? 'none';
|
|
319
|
+
return done(`pm:${pm}:${commands.length}cmds`);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* `batch/<id>-<YYYYMMDD>` off `base_branch`, and a fresh runstate run id
|
|
323
|
+
* minted against the anchor. The worktree is either a pool claim (already
|
|
324
|
+
* warm) or a cold `git worktree add` that this function then warms itself
|
|
325
|
+
* (#561, see the module doc) — either way, the worktree is warm before this
|
|
326
|
+
* returns `ok: true`. All-or-nothing: any failed step reports the step name
|
|
327
|
+
* and nothing is partially recorded on the batch.
|
|
236
328
|
*/
|
|
237
329
|
function runBatchSetup(deps, batch, now) {
|
|
238
330
|
if (batch.anchor === null)
|
|
@@ -255,6 +347,36 @@ function runBatchSetup(deps, batch, now) {
|
|
|
255
347
|
if (runId === null || runId.trim() === '')
|
|
256
348
|
return { ok: false, reason: 'runstate-mint-failed' };
|
|
257
349
|
const mintedRunId = runId.trim();
|
|
350
|
+
// Pool claim first — a claimed worktree is already warm by construction, so
|
|
351
|
+
// no separate warm step runs for it (AC2). `claim()` itself never pushes
|
|
352
|
+
// the branch (mirrors setup-issue-workflow's per-issue pool-claim step),
|
|
353
|
+
// so that still happens here on success.
|
|
354
|
+
const claimed = deps.exec(teardown_1.POOL_BIN, [...teardown_1.POOL_ARGS_PREFIX, 'claim', '--issue', String(batch.anchor), '--branch', branch], deps.repoDir);
|
|
355
|
+
const claimedWorktree = claimed?.trim();
|
|
356
|
+
// Same hardening `teardown.ts`'s destructive sinks apply to a worktree path
|
|
357
|
+
// (no NUL/newline, no unresolved `..`) — this one is our own CLI's stdout,
|
|
358
|
+
// not attacker input, but garbage here would otherwise be trusted verbatim
|
|
359
|
+
// as `BatchEntry.worktree` and used as a spawn/exec cwd for every member.
|
|
360
|
+
const claimIsUsable = !!claimedWorktree &&
|
|
361
|
+
!claimedWorktree.includes('\0') &&
|
|
362
|
+
!claimedWorktree.includes('\n') &&
|
|
363
|
+
path.isAbsolute(claimedWorktree) &&
|
|
364
|
+
path.resolve(claimedWorktree) === claimedWorktree &&
|
|
365
|
+
(deps.fsExists ?? ((p) => fs.existsSync(p)))(claimedWorktree);
|
|
366
|
+
if (claimedWorktree && claimIsUsable) {
|
|
367
|
+
if (deps.exec('git', ['push', '-u', 'origin', '--', branch], claimedWorktree) === null) {
|
|
368
|
+
// Return the claim rather than leaking a permanently `assigned` pool
|
|
369
|
+
// entry nothing else will ever reference — `BatchEntry` only records
|
|
370
|
+
// `worktree`/`pool_claimed` on the `ok: true` path below.
|
|
371
|
+
deps.exec(teardown_1.POOL_BIN, [...teardown_1.POOL_ARGS_PREFIX, 'return', '--path', claimedWorktree, '--json'], deps.repoDir);
|
|
372
|
+
return { ok: false, reason: 'branch-push-failed', runId: mintedRunId };
|
|
373
|
+
}
|
|
374
|
+
return { ok: true, branch, worktree: claimedWorktree, runId: mintedRunId, poolClaimed: true };
|
|
375
|
+
}
|
|
376
|
+
// A non-null, non-empty, unusable claim response (rather than a plain "no
|
|
377
|
+
// warm spares" null/empty) is unusual enough to note on the batch that
|
|
378
|
+
// otherwise cold-builds silently.
|
|
379
|
+
const poolNote = claimedWorktree ? 'pool-claim-invalid:' : '';
|
|
258
380
|
if (deps.exec('git', ['fetch', 'origin', '--', batch.base_branch], deps.repoDir) === null) {
|
|
259
381
|
return { ok: false, reason: 'fetch-failed', runId: mintedRunId };
|
|
260
382
|
}
|
|
@@ -267,7 +389,20 @@ function runBatchSetup(deps, batch, now) {
|
|
|
267
389
|
if (deps.exec('git', ['worktree', 'add', '--', worktree, branch], deps.repoDir) === null) {
|
|
268
390
|
return { ok: false, reason: 'worktree-add-failed', runId: mintedRunId };
|
|
269
391
|
}
|
|
270
|
-
|
|
392
|
+
const warmed = warmColdBatchWorktree(deps, batch, worktree, now, poolNote);
|
|
393
|
+
if (!warmed.ok) {
|
|
394
|
+
// Restore the "all-or-nothing" contract this function documents: a warm
|
|
395
|
+
// failure otherwise leaves the branch pushed and the worktree on disk,
|
|
396
|
+
// so the NEXT tick's retry dies at `branch-create-failed` forever
|
|
397
|
+
// instead of ever reaching warm-up again. Best-effort — a failed cleanup
|
|
398
|
+
// here just means the next retry's `worktree-add-failed`/`branch-create-failed`
|
|
399
|
+
// surfaces the leftover instead, no worse than before this cleanup existed.
|
|
400
|
+
deps.exec('git', ['worktree', 'remove', '--force', '--', worktree], deps.repoDir);
|
|
401
|
+
deps.exec('git', ['branch', '-D', branch], deps.repoDir);
|
|
402
|
+
deps.exec('git', ['push', 'origin', '--delete', branch], deps.repoDir);
|
|
403
|
+
return { ok: false, reason: warmed.reason, runId: mintedRunId };
|
|
404
|
+
}
|
|
405
|
+
return { ok: true, branch, worktree, runId: mintedRunId, poolClaimed: false };
|
|
271
406
|
}
|
|
272
407
|
/** Spawn one batch member's `slot-cycle` agent into the slot batch-setup (or a prior member) just released. */
|
|
273
408
|
/**
|
|
@@ -420,7 +555,12 @@ function claimAndSetup(deps, config, dispatch, batchId, now, result) {
|
|
|
420
555
|
poster(batch.anchor, setup.runId, {
|
|
421
556
|
phase: 'batch-setup',
|
|
422
557
|
status: 'done',
|
|
423
|
-
kv: {
|
|
558
|
+
kv: {
|
|
559
|
+
branch: setup.branch,
|
|
560
|
+
worktree: setup.worktree,
|
|
561
|
+
base_branch: batch.base_branch,
|
|
562
|
+
pool_claimed: setup.poolClaimed ? 'true' : 'false',
|
|
563
|
+
},
|
|
424
564
|
});
|
|
425
565
|
deps.journal.append((0, journal_1.unitEvent)('batch-setup-done', unit(batchId), { detail: setup.worktree }), now);
|
|
426
566
|
deps.store.withLock((s) => {
|
|
@@ -438,7 +578,12 @@ function claimAndSetup(deps, config, dispatch, batchId, now, result) {
|
|
|
438
578
|
});
|
|
439
579
|
return { state: releaseSlot(s, batchId, now), result: undefined };
|
|
440
580
|
}
|
|
441
|
-
let next = (0, state_1.patchBatch)(s, batchId, {
|
|
581
|
+
let next = (0, state_1.patchBatch)(s, batchId, {
|
|
582
|
+
branch: setup.branch,
|
|
583
|
+
worktree: setup.worktree,
|
|
584
|
+
run_id: setup.runId,
|
|
585
|
+
pool_claimed: setup.poolClaimed,
|
|
586
|
+
}, now);
|
|
442
587
|
next = (0, state_1.transitionBatch)(next, batchId, 'executing', { executing_member: 1 }, now);
|
|
443
588
|
const slot = slotFor(next, batchId);
|
|
444
589
|
if (slot)
|
|
@@ -590,8 +735,11 @@ function boundaryCommits(deps, batch) {
|
|
|
590
735
|
/**
|
|
591
736
|
* `validating`, no live slot: run the aggregate suite (deterministic — no
|
|
592
737
|
* agent, no slot claimed, matching AC5's "member or batch-LLM-step" wording).
|
|
593
|
-
* Green proceeds to the tail;
|
|
594
|
-
*
|
|
738
|
+
* Green proceeds to the tail; an unreadable report (never got a parseable
|
|
739
|
+
* report at all, distinct from a parseable one naming zero failures) blocks
|
|
740
|
+
* the batch instead of attributing (#562); a genuinely red, parseable report
|
|
741
|
+
* attributes and either fixes one offender or dissolves when nothing could be
|
|
742
|
+
* attributed (RFC F.2/F.8).
|
|
595
743
|
*/
|
|
596
744
|
function runValidate(deps, config, dispatch, batchId, now, result) {
|
|
597
745
|
const state = deps.store.load();
|
|
@@ -605,7 +753,7 @@ function runValidate(deps, config, dispatch, batchId, now, result) {
|
|
|
605
753
|
if (batch.anchor !== null && batch.run_id !== null) {
|
|
606
754
|
poster(batch.anchor, batch.run_id, { phase: 'batch-validate', status: 'done', kv: {} });
|
|
607
755
|
}
|
|
608
|
-
deps.journal.append((0, journal_1.unitEvent)('verify-complete', unit(batchId), { detail: 'suite green' }), now);
|
|
756
|
+
deps.journal.append((0, journal_1.unitEvent)('verify-complete', unit(batchId), { detail: suite.detail ?? 'suite green' }), now);
|
|
609
757
|
deps.store.withLock((s) => {
|
|
610
758
|
const b = (0, state_1.findBatch)(s, batchId);
|
|
611
759
|
if (!b || b.status !== 'validating')
|
|
@@ -615,6 +763,28 @@ function runValidate(deps, config, dispatch, batchId, now, result) {
|
|
|
615
763
|
spawnTailAgent(deps, config, dispatch, batchId, now, result);
|
|
616
764
|
return;
|
|
617
765
|
}
|
|
766
|
+
// #562: the suite report itself was unreadable (empty, unparseable, a
|
|
767
|
+
// spawn/timeout error) even after the runner's own fallback retry — NOT the
|
|
768
|
+
// same as a parseable report naming zero failures. Attribution would read
|
|
769
|
+
// `suite.failing` as "nothing to attribute" and dissolve a batch that may
|
|
770
|
+
// be fully green; block instead, preserving every member commit and the
|
|
771
|
+
// worktree, for an operator to inspect. `blocked` has no CLI resume verb
|
|
772
|
+
// yet — `sched abandon --batch` is today's only real exit.
|
|
773
|
+
if (suite.readable === false) {
|
|
774
|
+
deps.journal.append((0, journal_1.unitEvent)('suite-failed', unit(batchId), {
|
|
775
|
+
detail: suite.detail ?? 'suite report unreadable',
|
|
776
|
+
}), now);
|
|
777
|
+
const blocked = (0, recovery_1.blockBatch)(state, batchId, { reason: 'suite-unreadable' }, rDeps);
|
|
778
|
+
deps.store.withLock((s) => ({
|
|
779
|
+
state: applyBatchAndIssues(s, blocked.state, batchId, []),
|
|
780
|
+
result: undefined,
|
|
781
|
+
}));
|
|
782
|
+
// `result.blocked` is "issue numbers requeued full-cycle by a dissolve"
|
|
783
|
+
// (see `BatchTickResult`'s field doc) — a blocked batch requeues nothing,
|
|
784
|
+
// so it is reported only under `failed`, not under `blocked`.
|
|
785
|
+
result.failed.push(unit(batchId));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
618
788
|
const { state: attributed, outcome } = (0, recovery_1.beginAttribution)(state, batchId, { failing: suite.failing, footprints: memberFootprints(deps, batch) }, rDeps);
|
|
619
789
|
if (outcome.offenders.length === 0) {
|
|
620
790
|
const dissolve = (0, recovery_1.dissolveBatch)(attributed, batchId, { strategy: 'full', reason: 'unattributable-suite-failure' }, rDeps);
|
|
@@ -1037,14 +1207,18 @@ function teardownBatch(deps, batchId) {
|
|
|
1037
1207
|
if (!batch || batch.worktree === null)
|
|
1038
1208
|
return;
|
|
1039
1209
|
const root = deps.exec('git', ['rev-parse', '--show-toplevel'], deps.repoDir) ?? deps.repoDir;
|
|
1040
|
-
|
|
1210
|
+
// Pool-claimed worktrees are validated by the pool's own `return` (it only
|
|
1211
|
+
// accepts paths in pool state) and can legitimately live outside either
|
|
1212
|
+
// `isSafeWorktree` root when `.worktree-pool.json` configures a custom
|
|
1213
|
+
// `pool_dir` — mirrors `runTeardown`'s own internal skip for `poolClaimed`.
|
|
1214
|
+
if (batch.pool_claimed !== true && !(0, teardown_1.isSafeWorktree)(path.resolve(root), batch.worktree)) {
|
|
1041
1215
|
journalEvent(deps, 'teardown-failed', unit(batchId), {
|
|
1042
1216
|
reason: 'unsafe-worktree-path',
|
|
1043
1217
|
detail: batch.worktree,
|
|
1044
1218
|
});
|
|
1045
1219
|
return;
|
|
1046
1220
|
}
|
|
1047
|
-
const result = (0, teardown_1.runTeardown)(deps.exec, deps.repoDir, { worktree: batch.worktree, poolClaimed:
|
|
1221
|
+
const result = (0, teardown_1.runTeardown)(deps.exec, deps.repoDir, { worktree: batch.worktree, poolClaimed: batch.pool_claimed === true, branch: batch.branch }, deps.fsExists);
|
|
1048
1222
|
journalEvent(deps, result.cleanup === 'done' ? 'teardown-done' : 'teardown-failed', unit(batchId), {
|
|
1049
1223
|
cleanup: result.cleanup,
|
|
1050
1224
|
detail: result.detail,
|