@ai-dossier/sched 0.7.1 → 0.9.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
@@ -97,9 +97,10 @@ where every mechanical supervision decision is code, not remembered prose:
97
97
  5. **Immediate refill (AC5)** — a slot freed by a terminal state is refilled in the SAME
98
98
  tick; a runnable unit never waits while a slot is idle (pinned by a regression test).
99
99
  6. **Journal (AC6)** — every event (assigned, spawned, exit-detected, external-advance,
100
- progress, stalled, redispatched, unit-failed, dependents-blocked, suspect-dispatch,
101
- dispatch-unhealthy, …) is appended to `events.jsonl`; `sched status` shows the live
102
- phase per unit. `label-blocked`/`label-check-failed` (#507) are the one pair journaled
100
+ progress, stalled, redispatched, fence-written, fence-failed, unit-failed,
101
+ dependents-blocked, suspect-dispatch, dispatch-unhealthy, …) is appended to
102
+ `events.jsonl`; `sched status` shows the live phase per unit, plus each slot's `gen`
103
+ and `fenced` state (#504). `label-blocked`/`label-check-failed` (#507) are the one pair journaled
103
104
  OUTSIDE the engine — `sched enqueue` appends them at enqueue time, before dispatch.
104
105
  7. **Dispatch-health pause (#505)** — an unverified exit within `SUSPECT_DISPATCH_WINDOW_MS`
105
106
  (60s) of a slot's last progress is `suspect-dispatch`: real work rarely produces zero
@@ -132,17 +133,63 @@ Two engine-safety policies were explicit product decisions on #464:
132
133
  an outage holds in `verifying` until truth returns. Each pause is journaled as
133
134
  `ground-truth-unreachable`.
134
135
 
135
- Only `issue:<n>` units are dispatched today — batch member sequencing is a follow-up
136
- (#464 non-goal).
136
+ This applies to `issue:<n>` unit dispatch (`dispatchAssignments`). `batch:<id>` units run
137
+ through a separate pass with its own claim/reconcile logic — see
138
+ [Batch dispatch (#523)](#batch-dispatch-523) below.
139
+
140
+ ### Zombie-run fencing (#504)
141
+
142
+ The ladder redispatches the SAME run, so a takeover inherits the run id and its milestone
143
+ trail. In the #472 race that turned out to be a hole: `enterRecovery` kills the pid it
144
+ knows about, but an agent it cannot see or signal — throttled, cwd outside the worktree —
145
+ survives, and nothing on the trail tells that agent it was replaced. Both runs implemented
146
+ the same issue, and both kept posting milestones on one trail. The doctrine, one step past
147
+ "an agent exiting is not proof of merge": **no visible process is not proof of death.**
148
+
149
+ A **generation** now fences the trail:
150
+
151
+ - Before the takeover is spawned, the engine calls `ai-dossier runstate fence`, which
152
+ posts a `status=superseded` milestone carrying `gen=<n>` and `takeover=<label>`.
153
+ Written first, on purpose, so it survives the takeover dying too.
154
+ - The takeover is told its generation in its prompt and passes `--gen <n>` to every
155
+ `runstate post`. **The CLI refuses any post below the trail's fenced generation**, so
156
+ the superseded agent cannot extend the trail even though it never checks — and an agent
157
+ running an older dossier implicitly sits at generation 0, fenced out the moment
158
+ generation 1 exists.
159
+ - `ai-dossier runstate check --issue <n> --run <id> --gen <g>` exits `3` when the caller
160
+ has been superseded: the checkpoint a workflow runs before implement, review, and ship.
161
+ - A takeover that posts NOTHING is watched on the **shorter** of
162
+ `fence_takeover_timeout_ms` (default 15 min) and the phase's own stall allowance — the
163
+ fence window can only ever bring recovery forward, never delay it — so a takeover that
164
+ dies at birth re-enters the ladder in minutes and the next fence supersedes it in turn.
165
+ The first progress signal disarms the short window. `ESCALATION_CAP` still bounds the
166
+ whole ladder.
167
+ - Report agents ride the same rail: a fenced report slot is told its generation too, or
168
+ its `report done` milestone would be refused and it would recover to the cap on a PR
169
+ that already merged.
170
+ - The read side is hardened, because a milestone is an issue comment: only comments from
171
+ an account with **write access** count as a fence, a forged `takeover=` label is dropped
172
+ rather than echoed into an agent's prompt, and the engine refuses to fence a run id that
173
+ does not belong to the issue it is working on (that would journal success while the real
174
+ zombie stayed free to write).
175
+
176
+ Fencing is defense-in-depth, not a precondition: if the fence cannot be written (no run id
177
+ on the trail yet, gh unreachable, no fencer configured) the redispatch proceeds unfenced
178
+ and journals `fence-failed`. Stranding a stalled unit forever would be the worse failure —
179
+ but the unprotected redispatch is never silent.
137
180
 
138
181
  ## Batch failure recovery (#472)
139
182
 
140
183
  What happens when a batch's aggregate suite goes red, or its PR will not merge
141
184
  (RFC-0001 §F.2/F.8/F.9).
142
185
 
143
- **Not yet wired into `sched start`** — `tick()` still dispatches only `issue:<n>` units
144
- (the batch execution loop is a follow-up, see above). These modules are the library
145
- surface that loop will call, and they are tested standalone against real scratch repos.
186
+ **Wired into `sched start` since #523** the `validating attributing fixing/evicting`
187
+ rail below is called directly from `batch-dispatch.ts`'s `runValidate`/`evictOffender`
188
+ (a red AGGREGATE suite, after every member individually went green). A member that never
189
+ went green in the first place (its own gate failed) evicts through a separate, simpler
190
+ rail that never touches this module — see
191
+ [Batch dispatch (#523)](#batch-dispatch-523). These modules remain independently tested
192
+ against real scratch repos.
146
193
 
147
194
  ```
148
195
  validating → attributing → fixing (ONE bounded attempt) → validating
@@ -215,6 +262,12 @@ a report slot in the first place) with the persisted `phase` as a fallback when
215
262
  matching entry exists. A backfilled role is a best-effort inference, not a guarantee —
216
263
  see `validateState` in `state.ts` for the exact rule.
217
264
 
265
+ Schema 1.6.0: `SlotEntry` gains `gen` (number — the runstate generation the slot's agent
266
+ owns, 0 for a first dispatch) and `fenced_at` (ISO string or null — set when a takeover is
267
+ fenced in, cleared by its first progress signal; #504 above). 1.5.0 states migrate on
268
+ load: nothing was fenced before fencing existed, so `0`/`null` is the exact backfill, not
269
+ a guess. Both reset with the slot on release (`CLEARED_SLOT_FIELDS`).
270
+
218
271
  Schema 1.5.0: `SchedState` gains `consecutive_suspect_dispatches` (number) and
219
272
  `last_suspect_dispatch_unit` (string or null) — the dispatch-health pause's cross-unit
220
273
  suspect-dispatch streak (#505 above). The two fields are a single fact and must agree
@@ -222,6 +275,64 @@ suspect-dispatch streak (#505 above). The two fields are a single fact and must
222
275
  on load: no suspect dispatches were ever tracked under them, so `0`/`null` is the exact
223
276
  backfill, not a guess.
224
277
 
278
+ ## Batch dispatch (#523)
279
+
280
+ `batch-dispatch.ts`'s `runBatchTick` — called from `tick()` after the issue-level pass,
281
+ only when `batchExec`/`runBatchSuite` are both configured on `EngineDeps` — drives every
282
+ `batch:<id>` unit through:
283
+
284
+ ```
285
+ ready → executing(member i/N) ⟲ → validating → reviewing → shipping
286
+ → awaiting-merge → merged → deployed → reported → done
287
+ failure rails: executing → dissolving (a member self-reports blocked)
288
+ validating → attributing → (fixing | evicting) → validating → dissolving
289
+ ```
290
+
291
+ - **One shared worktree/branch per batch**, claimed once by a deterministic (no LLM)
292
+ `batch-setup` step: `git branch`/`push`/`worktree add` off `base_branch`, named
293
+ `batch/<id>-<date>`, plus a fresh `ai-dossier runstate mint` against the anchor issue.
294
+ - **Members run serially, one fresh `slot-cycle` agent at a time**, in the shared
295
+ worktree. A member's completion signal is `phase=review status=done mode=slot` on its
296
+ OWN issue (`slot-cycle` posts no phase of its own past `review` — ship is batch-owned);
297
+ its commit range on the batch branch is recomputed (`git log`) after every member and
298
+ kept on `BatchEntry.ranges` for eviction. An incremental gate (`ai-dossier cap run
299
+ typecheck.run` / `test.focused`, when the repo has a manifest) runs after each member
300
+ before advancing — a second, independent check that the member's self-reported "done"
301
+ is real.
302
+ - **The batch's single slot is claimed FRESH for each live step** (a member, the tail
303
+ agent, the report agent, a bounded fix agent) — never held across a wait. The aggregate
304
+ suite itself runs with NO slot claimed at all (deterministic engine work, not an LLM
305
+ step).
306
+ - **Two failure rails.** A member that never went green evicts directly (nothing to
307
+ attribute — see the #472 section above for what "directly" skips). A red AGGREGATE
308
+ suite (every member individually green, but integration-level conflict) routes through
309
+ the #472 attribution/fix/evict library.
310
+ - **The tail**, after the last member: the aggregate suite runs deterministically; green
311
+ spawns ONE bounded strong-tier agent that runs `review-issue` aggregate mode then
312
+ `ship-issue` batch mode (rebase-merge, a `Closes` list) and parks the PR exactly like a
313
+ detached full-cycle run; the engine's own PR watcher (a batch-granularity mirror of the
314
+ per-issue one) accepts the merge and dispatches a cheap mechanical-tier agent for
315
+ `report-issue`'s batch variant.
316
+ - **Scope cuts, recorded rather than discovered later:** no `git bisect` stage for an
317
+ ambiguous aggregate failure (an unattributable red suite dissolves instead); no
318
+ worktree-pool integration for batch-setup (cold `git worktree add` only); no per-phase
319
+ stall/escalation ladder for batch sub-agents (a dead-without-verification agent is
320
+ treated as blocked, not redispatched stronger).
321
+
322
+ Schema 1.7.0: `BatchEntry` gains `worktree` (absolute path of the shared batch worktree,
323
+ null until batch-setup lands), `ranges` (`MemberRange[]` — each member's commit range,
324
+ recomputed after every member completes) and `pr` (the batch PR parked on auto-merge,
325
+ persisted so a restart mid-watch still knows what to poll). 1.6.0 states migrate on load:
326
+ no batch was ever dispatched under them, so `null`/`[]`/`null` is the exact backfill, not
327
+ a guess. Config schema moves to 1.3.0: `dispatch` gains `member_prompt`,
328
+ `batch_tail_prompt` and `batch_report_prompt` (the three new agent prompt templates).
329
+
330
+ New journal events: `batch-setup-done`, `batch-setup-failed`, `member-advanced`. Member/
331
+ tail/report/fix-agent spawn, progress, completion and park events reuse the existing
332
+ unit-generic names (`assigned`/`spawned`/`unit-failed`/`external-advance`/`pr-parked`/
333
+ `merge-accepted`/`report-dispatched`/`teardown-done`/`teardown-failed`) with
334
+ `unit = batch:<id>`.
335
+
225
336
  ## API surface
226
337
 
227
338
  ```ts
@@ -236,17 +347,29 @@ import {
236
347
  runLoop, // the sched start loop (tick, sleep, repeat)
237
348
  type TickResult, // what one tick did (spawned/parked/merge-accepted/stale-reconciled/
238
349
  // dependents-unblocked/report-dispatched/teardown/completed/
239
- // redispatched/failed/blocked)
350
+ // redispatched/failed/blocked) — since #523 also carries
351
+ // `batch:<id>` unit ids (issue numbers for `blocked`)
240
352
  type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground
241
- // truth/clock/repoDir/teardownExec)
353
+ // truth/clock/repoDir/teardownExec/fencer/batchExec/runBatchSuite/
354
+ // runBatchCapability — #523)
242
355
  createSpawnDeps, // real detached-spawn process I/O
243
356
  createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec);
244
357
  // since #468 also gh pr view PR state + setup info from comments
245
358
  resolveDispatch, // config → resolved command/prompt/report-prompt/tier-models/timers
246
359
  stallTimeoutForPhase, // the stall allowance for the phase now in flight (#495 per-phase
247
360
  // map → global, hardened against a prototype-name phase)
361
+ stallTimeoutForSlot, // #504: that allowance, shortened to fenceTakeoverTimeoutMs while
362
+ // a takeover has posted nothing (Math.min — never longer)
363
+ takeoverInstruction, // the TAKEOVER prompt suffix appended for gen > 0
364
+ SUPERSESSION_CHECKPOINT_INSTRUCTION, // the check-before-implement/review/ship clause
365
+ // every dispatch prompt carries
366
+ createExecRunFencer, // default fencer: shells `ai-dossier runstate fence --json`
367
+ parseFenceGeneration, // fence stdout → the installed generation (null = unfenced)
368
+ type RunFencer, // inject the takeover-record writer: (issue, run, phase, takeover)
369
+ type FenceOutcome, // {ok, gen} | {ok: false, reason} — a failure carries its cause
370
+ FENCE_TIMEOUT_MS, // fence subprocess timeout (60 s — two gh round trips)
248
371
  DEFAULT_PHASE_STALL_TIMEOUT_MS, // built-in per-phase stall allowances (implement: 90 min)
249
- buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup} substituted)
372
+ buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup}/{gen} substituted)
250
373
  reportTierFor, // report (re)dispatch tier after N escalations
251
374
  isParkedMilestone, // ship-phase awaiting-merge + pr= → the park signal
252
375
  prOfMilestone, // a milestone's pr= key as a positive integer
@@ -279,9 +402,24 @@ import {
279
402
  transitionIssue, transitionBatch, transitionSlot, // typed §D transitions
280
403
  TRANSITIONS, // the transition tables themselves (for previews)
281
404
  buildStatusReport, // machine-readable status incl. blocked/failed sets
282
- validateState, // strict persisted-state validation (1.0.0-1.4.0 files migrate)
405
+ validateState, // strict persisted-state validation (1.0.0-1.6.0 files migrate)
283
406
  IllegalTransitionError, EnqueueError, CorruptStateError, LockTimeoutError,
284
407
  SchedNotFoundError,
408
+ runBatchTick, // #523: one batch reconcile+refill pass; called by tick() after
409
+ // the issue pass — loads/saves state itself, holds no lock
410
+ // across the call
411
+ type BatchDispatchDeps, // inject store/journal/groundTruth/spawnDeps/exec/runSuite/
412
+ // runCapability(optional)/fsExists(optional)
413
+ type BatchTickResult, // spawned/completed/parked/mergeAccepted/failed (batch:<id> ids)
414
+ // + blocked (issue numbers, dissolve-requeued)
415
+ type CapOutcome, // ok | task-failed | automation-broken | capability-unavailable
416
+ buildMemberPrompt, buildBatchTailPrompt, buildBatchReportPrompt, // #523 prompt builders
417
+ DEFAULT_MEMBER_PROMPT_TEMPLATE, DEFAULT_BATCH_TAIL_PROMPT_TEMPLATE,
418
+ DEFAULT_BATCH_REPORT_PROMPT_TEMPLATE,
419
+ isMemberComplete, isMemberBlocked, // member milestone predicates (mode=slot gated)
420
+ isBatchTailParked, // batch-ship awaiting-merge + pr= — the batch park signal
421
+ isBatchPhaseDone, // <phase> done on the anchor (batch-review/batch-report)
422
+ batchOfUnit, // batch:<id> → <id>; null for issue units or malformed ids
285
423
  } from '@ai-dossier/sched';
286
424
  ```
287
425
 
@@ -297,7 +435,8 @@ fake agents and stub ground truth; no LLM calls anywhere.
297
435
  ~/.dossier/sched/<project>/
298
436
  ├── state.json # hot operational truth — atomic tmp+fsync+rename writes;
299
437
  ├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms,
300
- │ # pr_poll_interval_ms, dispatch (incl. report_prompt, phase_stall_timeout_ms)
438
+ │ # pr_poll_interval_ms, dispatch (incl. report_prompt,
439
+ │ # phase_stall_timeout_ms, fence_takeover_timeout_ms)
301
440
  ├── events.jsonl # append-only event journal (the operator's flight recorder)
302
441
  ├── runs/ # per-unit agent output logs (issue-<n>.log)
303
442
  └── .sched-lock/ # cross-process directory mutex (pid; stolen from dead holders)
@@ -312,13 +451,13 @@ fake agents and stub ground truth; no LLM calls anywhere.
312
451
  never a silent queue reset. `state.json` is deletable and rebuildable from GitHub,
313
452
  which remains the system of record.
314
453
  - **Schema**: state/config files from #460 (schema 1.0.0), #464 (1.1.0), #468 (1.2.0),
315
- #472 (1.3.0) and #500 (1.4.0) load and migrate to 1.5.0 automatically (slot
454
+ #472 (1.3.0), #500 (1.4.0) and #505 (1.5.0) load and migrate to 1.6.0 automatically (slot
316
455
  `branch`/`last_head`/`pid_start`, slot `role` (inferred from the unit's queue entry,
317
456
  with the persisted `phase` as a fallback — #500), entry `pr`/`cleanup`/
318
457
  `failure_evidence`, batch `anchor`/`branch`/`run_id`/`eviction_groups`/`evictions`/
319
458
  `fix_attempts`/`rebase_attempts`, state-level `last_pr_poll_at` backfill to null, and
320
459
  state-level `consecutive_suspect_dispatches`/`last_suspect_dispatch_unit` backfill to
321
- `0`/`null` — #505).
460
+ `0`/`null` — #505, and slot `gen`/`fenced_at` backfill to `0`/`null` — #504).
322
461
  - **`max_slots`** bounds live units (`assigned | running | recovering`); dependency
323
462
  edges gate readiness — an issue with an unmerged dependency, and a batch behind an
324
463
  unmerged batch, are never runnable.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Batch dispatch (#523, RFC-0001 §C.4/D.2/D.3): the missing driver that
3
+ * executes `batch:<id>` units. #498 landed the batch failure-recovery library
4
+ * (attribution/bisect/eviction/dissolve, `recovery.ts`) and the batch state
5
+ * machine (`state.ts`); readiness/placement already treat a `ready` batch as
6
+ * a runnable unit (`readiness.ts`, `scheduler.ts`). Nothing dispatched one
7
+ * until now.
8
+ *
9
+ * Shape, mirroring `engine.ts`'s per-issue dispatch: claim a slot → spawn an
10
+ * agent → poll ground truth → verify → transition. Generalized to `BatchEntry`
11
+ * at batch-phase granularity instead of per-issue-phase granularity:
12
+ *
13
+ * ```
14
+ * ready → executing(member i/N) ⟲ → validating → reviewing → shipping
15
+ * → awaiting-merge → merged → deployed → reported → done
16
+ * failure rails (RFC F.2/F.8/F.9):
17
+ * executing → dissolving (a member self-reports blocked, RFC F.1)
18
+ * validating → attributing → (fixing | evicting) → validating
19
+ * → dissolving
20
+ * ```
21
+ *
22
+ * NO batch claim — not the first (`ready → executing`) nor any continuation
23
+ * (a later member, the tail agent, the report agent, the fix agent) — ever
24
+ * goes through `computeAssignments`/`runnableUnits`. Every one is a bespoke
25
+ * free-capacity-gated assignment, the same shape `engine.ts`'s
26
+ * `dispatchReportAgents` already uses (`runnableUnits` only ever offers a
27
+ * `status === 'ready'` batch, i.e. the moment BEFORE any claim). Between
28
+ * steps — a suite run, a PR merge wait — the slot is released to `idle` and
29
+ * holds no capacity (AC5): only a live member/tail/report/fix agent holds a
30
+ * slot.
31
+ *
32
+ * The aggregate suite itself is deterministic engine work, not an LLM step —
33
+ * it runs with no slot claimed at all, matching AC5's "member or batch-LLM-step"
34
+ * wording precisely.
35
+ *
36
+ * Two distinct failure rails, deliberately different:
37
+ * - A member's OWN agent reports itself blocked (its own gate never went
38
+ * green) — evicted directly, no attribution needed: the offender is already
39
+ * known, and either it has no commits yet (blocked before implementing) or
40
+ * its commits are exactly what gets reverted.
41
+ * - The AGGREGATE suite (run by the engine after every member individually
42
+ * went green) comes back red — an integration-level conflict no member's own
43
+ * gate caught. THIS is what `recovery.ts`'s attribution/fix/evict pipeline
44
+ * exists for (RFC F.2).
45
+ *
46
+ * Scope decisions recorded here, not silently cut: no `git bisect` stage for
47
+ * an ambiguous aggregate failure (bisect needs a per-project "run only these
48
+ * tests" command this module has no generic way to construct) — an
49
+ * unattributable red aggregate suite dissolves the batch rather than
50
+ * bisecting, which `attributing → dissolving` already models. No worktree-pool
51
+ * integration for batch-setup — cold `git worktree add` only, mirroring
52
+ * `teardown.ts`'s cold path. No per-phase stall/escalation ladder for batch
53
+ * sub-agents — a dead-without-verification agent is treated as blocked and
54
+ * evicted/reported rather than redispatched stronger. Both are documented
55
+ * follow-ups, not gaps discovered later.
56
+ */
57
+ import { type ResolvedDispatch, type SpawnDeps } from './dispatch';
58
+ import { type GroundTruth } from './groundtruth';
59
+ import { type Journal } from './journal';
60
+ import type { SchedStore } from './persist';
61
+ import type { ExecFn } from './project';
62
+ import { type SuiteResult } from './recovery';
63
+ import { type FsExists } from './teardown';
64
+ import type { SchedConfig } from './types';
65
+ /**
66
+ * The four `ai-dossier cap run` outcomes (docs/reference/capabilities.md):
67
+ * `ok` = the capability ran and passed; `task-failed` = it ran and the TASK
68
+ * itself failed (trust the result); `automation-broken` = do not trust the
69
+ * machinery (missing tool, timeout, bad manifest); `capability-unavailable` =
70
+ * no manifest / no such id / `lifecycle: shadow` — no fast path here.
71
+ */
72
+ export type CapOutcome = 'ok' | 'task-failed' | 'automation-broken' | 'capability-unavailable';
73
+ /** Everything batch dispatch needs from the outside world. */
74
+ export interface BatchDispatchDeps {
75
+ store: SchedStore;
76
+ journal: Journal;
77
+ groundTruth: GroundTruth;
78
+ spawnDeps: SpawnDeps;
79
+ now: () => Date;
80
+ /** Repo root — cwd for `git`/`ai-dossier` calls that are not batch-worktree-scoped. */
81
+ repoDir: string;
82
+ /** Exec for batch git/milestone-CLI operations (never throws — the `ExecFn` contract). */
83
+ exec: ExecFn;
84
+ /** Runs the aggregate suite inside a batch worktree; batches never leave `validating` without one. */
85
+ runSuite: (worktree: string) => SuiteResult;
86
+ /**
87
+ * Runs one `ai-dossier cap run <capabilityId>` in a batch worktree for the
88
+ * per-member incremental gate (#523 AC2 — "typecheck + focused tests via
89
+ * `cap run test.focused` when available"). Optional and degrade-not-crash,
90
+ * like `batchExec`/`runBatchSuite`: without it, or on `automation-broken`/
91
+ * `capability-unavailable`, the gate is skipped — the member's own
92
+ * `slot-cycle` run already attempted this fast path (with its own reasoning
93
+ * fallback) before ever posting `review done`, so a repo with no manifest
94
+ * loses nothing but the engine's independent re-check.
95
+ */
96
+ runCapability?: (worktree: string, capabilityId: string) => CapOutcome;
97
+ fsExists?: FsExists;
98
+ }
99
+ /** What one `runBatchTick` call did, merged into `engine.ts`'s `TickResult` by the caller. */
100
+ export interface BatchTickResult {
101
+ spawned: string[];
102
+ completed: string[];
103
+ parked: string[];
104
+ mergeAccepted: string[];
105
+ failed: string[];
106
+ /** Issue numbers requeued full-cycle by a dissolve — matches `TickResult.blocked`'s shape. */
107
+ blocked: number[];
108
+ }
109
+ /**
110
+ * One batch reconcile+refill pass. Called from `engine.ts`'s `tick()` after
111
+ * the issue-level pass — batches never compete with issues for a slot within
112
+ * the same tick because this pass runs strictly after `dispatchAssignments`
113
+ * already filled every slot it could (see the module doc: batch claims never
114
+ * go through `computeAssignments`/`runnableUnits` at all). Loads and saves
115
+ * state itself via `deps.store.withLock` — the caller holds no lock across
116
+ * this call. `deps.exec` and `deps.runSuite` are mandatory; `deps.
117
+ * runCapability` is independently optional (AC2's incremental gate is itself
118
+ * a "when available" fast path).
119
+ */
120
+ export declare function runBatchTick(deps: BatchDispatchDeps, config: SchedConfig, dispatch: ResolvedDispatch): BatchTickResult;
121
+ //# sourceMappingURL=batch-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAWH,OAAO,EAKL,KAAK,gBAAgB,EACrB,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,EASL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,KAAK,QAAQ,EAA+B,MAAM,YAAY,CAAC;AACxE,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,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;OASG;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;AAyuCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsEjB"}