@ai-dossier/sched 0.5.2 → 0.7.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
@@ -81,7 +81,19 @@ where every mechanical supervision decision is code, not remembered prose:
81
81
  `stall_timeout_ms` (default 30 min) → kill the agent and redispatch the same unit one
82
82
  tier stronger (mechanical → mid → strong; the resume rails carry work forward). Cap 2
83
83
  escalations — or a stall at the strongest tier — fails the unit and blocks its
84
- TRANSITIVE dependents (`dep-failed:<issue>`).
84
+ TRANSITIVE dependents (`dep-failed:<issue>`). The timeout is **phase-aware** (#495):
85
+ the `implement` phase alone can run 1-3h on a large monorepo with zero intermediate
86
+ milestone or pushed commit, so it gets a longer built-in default (90 min,
87
+ `DEFAULT_PHASE_STALL_TIMEOUT_MS`) than every other phase's 30-min default — selected by
88
+ the phase now IN FLIGHT (the last milestone's `next=`, not the last completed phase;
89
+ before any milestone posts it falls back to the slot's own phase). A built-in phase
90
+ default is a FLOOR against the global `stall_timeout_ms` — raising the global never
91
+ silently shortens `implement`'s allowance. Override any phase via
92
+ `dispatch.phase_stall_timeout_ms: { "<phase>": <ms> }` in `config.json` (validated
93
+ against the known phase vocabulary — an unrecognized key is a config error, not a
94
+ silent no-op); an explicit override always wins verbatim, even below the built-in
95
+ default. A phase not listed keeps its built-in default (floored by the global) or
96
+ falls back to the global `stall_timeout_ms` outright.
85
97
  5. **Immediate refill (AC5)** — a slot freed by a terminal state is refilled in the SAME
86
98
  tick; a runnable unit never waits while a slot is idle (pinned by a regression test).
87
99
  6. **Journal (AC6)** — every event (assigned, spawned, exit-detected, external-advance,
@@ -222,14 +234,18 @@ import {
222
234
  tick, // one engine cycle: reconcile + verify + refill + spawn,
223
235
  // and since #468: park-watch, teardown, report dispatch
224
236
  runLoop, // the sched start loop (tick, sleep, repeat)
225
- type TickResult, // what one tick did (spawned/parked/merge-accepted/report-dispatched/
226
- // teardown/completed/redispatched/failed/blocked)
237
+ type TickResult, // what one tick did (spawned/parked/merge-accepted/stale-reconciled/
238
+ // dependents-unblocked/report-dispatched/teardown/completed/
239
+ // redispatched/failed/blocked)
227
240
  type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground
228
241
  // truth/clock/repoDir/teardownExec)
229
242
  createSpawnDeps, // real detached-spawn process I/O
230
243
  createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec);
231
244
  // since #468 also gh pr view PR state + setup info from comments
232
245
  resolveDispatch, // config → resolved command/prompt/report-prompt/tier-models/timers
246
+ stallTimeoutForPhase, // the stall allowance for the phase now in flight (#495 per-phase
247
+ // map → global, hardened against a prototype-name phase)
248
+ DEFAULT_PHASE_STALL_TIMEOUT_MS, // built-in per-phase stall allowances (implement: 90 min)
233
249
  buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup} substituted)
234
250
  reportTierFor, // report (re)dispatch tier after N escalations
235
251
  isParkedMilestone, // ship-phase awaiting-merge + pr= → the park signal
@@ -281,7 +297,7 @@ fake agents and stub ground truth; no LLM calls anywhere.
281
297
  ~/.dossier/sched/<project>/
282
298
  ├── state.json # hot operational truth — atomic tmp+fsync+rename writes;
283
299
  ├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms,
284
- │ # pr_poll_interval_ms, dispatch (incl. report_prompt)
300
+ │ # pr_poll_interval_ms, dispatch (incl. report_prompt, phase_stall_timeout_ms)
285
301
  ├── events.jsonl # append-only event journal (the operator's flight recorder)
286
302
  ├── runs/ # per-unit agent output logs (issue-<n>.log)
287
303
  └── .sched-lock/ # cross-process directory mutex (pid; stolen from dead holders)
@@ -356,12 +372,30 @@ prompt instructs it) and exit. The engine owns everything after the park:
356
372
  journal event — the work is merged, so dependents are never re-blocked.
357
373
  The full-cycle tail-run pattern (re-dispatching a whole run for
358
374
  teardown+report) is retired.
375
+ 7. **Stale-failure reconcile (#501)** — a `failed reason=auto-merge-blocked`
376
+ entry stops being watched the instant it leaves `parked`, but an operator
377
+ can manually clear the watcher's block and re-queue the same PR outside
378
+ the engine entirely. `pollParkedPrs` also polls these stale-failed entries
379
+ on the same cadence — no second poll pass, though each watched entry still
380
+ costs its own `gh pr view`/`gh issue view` — for up to 7 days after the
381
+ failure (`STALE_RECONCILE_WINDOW_MS`); past that an abandoned failure is
382
+ left alone rather than polled forever. Once the PR shows `MERGED` with
383
+ `mergedAt` set and the issue closed (the same three-part gate as AC1
384
+ above), the entry flips `failed → shipped`, re-enters the normal
385
+ teardown → report-dispatch path (items 5–6 above), and unblocks whatever
386
+ dependents `blockTransitiveDependents` wedged on the original (now
387
+ reversed) failure — `sched status` stops listing it under `failed`, and
388
+ both the reconcile and the unblock are journaled as
389
+ `stale-failure-reconciled`. A merged PR is accepted ahead of any failure
390
+ check even while it still carries a stale `auto-merge-blocked` label
391
+ (GitHub does not clear labels on merge) — checking the label first would
392
+ fail, then immediately un-fail, a unit that never actually failed.
359
393
 
360
394
  `sched status` shows parked PRs (zero slots, with the last poll's age), a
361
395
  `pr` column and a `cleanup` column on the queue; every watcher decision lands
362
396
  in `events.jsonl` (`pr-parked`, `merge-accepted`, `pr-watch-failed`,
363
397
  `pr-watch-waiting`, `teardown-done`/`teardown-failed`, `report-dispatched`,
364
- `report-failed`, `ground-truth-unreachable`).
398
+ `report-failed`, `ground-truth-unreachable`, `stale-failure-reconciled`).
365
399
 
366
400
  ## Development
367
401
 
@@ -68,13 +68,39 @@ export interface ResolvedDispatch {
68
68
  fixPrompt: string;
69
69
  /** Model per tier; null means "no model flag" (the command's `--model {model}` pair drops). */
70
70
  tierModels: Record<ModelTier, string | null>;
71
+ /** Global stall timeout — the fallback used when the in-flight phase has no entry in `phaseStallTimeoutMs` (#495). */
71
72
  stallTimeoutMs: number;
73
+ /**
74
+ * Per-phase stall timeout overrides (#495): built-in defaults merged with
75
+ * (and overridden by) operator config. A built-in phase default is never
76
+ * silently shortened by a smaller `stallTimeoutMs` (see `resolveDispatch`'s
77
+ * `Math.max` floor) — only an explicit operator override for that phase
78
+ * can lower it.
79
+ */
80
+ phaseStallTimeoutMs: Readonly<Record<string, number>>;
72
81
  reconcileIntervalMs: number;
73
82
  /** Parked-PR poll interval (#468 AC1, default 150 s — "every 2–3 min"). */
74
83
  prPollIntervalMs: number;
75
84
  }
76
85
  /** Resolve engine dispatch settings from the (possibly sparse) config. */
77
86
  export declare function resolveDispatch(config: SchedConfig): ResolvedDispatch;
87
+ /**
88
+ * The stall timeout to apply for `phase` (#495): a per-phase override (built-
89
+ * in default or operator config, `ResolvedDispatch.phaseStallTimeoutMs`) when
90
+ * one exists for `phase`, else the global `stallTimeoutMs`. `phase` should be
91
+ * the CURRENTLY RUNNING phase — the last milestone's `next=` — not the last
92
+ * COMPLETED phase (`slot.phase`), which lags one phase behind. `next=` may
93
+ * also legally be the terminal sentinel `'done'`, which never matches an
94
+ * override and therefore uses the global timeout.
95
+ *
96
+ * `Object.hasOwn` (not a plain `[]` lookup) guards against `phase` being an
97
+ * `Object.prototype` member name (`toString`, `constructor`, …): `next=` is
98
+ * parsed from a GitHub issue comment, which this repo's own threat model
99
+ * treats as untrusted (anyone who can comment can post a milestone-shaped
100
+ * comment), so an attacker-chosen `next=toString` must not resolve to a
101
+ * function and silently disable the stall check via a `NaN` comparison.
102
+ */
103
+ export declare function stallTimeoutForPhase(dispatch: ResolvedDispatch, phase: string | null): number;
78
104
  /**
79
105
  * Build the concrete agent argv for one dispatch: substitute `{issue}` and
80
106
  * `{model}`. When the tier's model is null, the `{model}` item AND its
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,EAML,KAAK,SAAS,EACd,KAAK,WAAW,EAGjB,MAAM,SAAS,CAAC;AAEjB,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAInE,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAOrD,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAQtD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,QAItB,CAAC;AAOtB;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAOnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,8BAA8B,QAMX,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,QASvC,CAAC;AAQF,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,YAAY,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,sHAAsH;IACtH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,CAkBrE;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK7F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,GACrD,MAAM,EAAE,CAgBV;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,GACd,MAAM,CAKR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,CAmBR;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAElE;AAID,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9D;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnD;;;OAGG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtD;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAyEvD"}
package/dist/dispatch.js CHANGED
@@ -52,6 +52,7 @@ var __importStar = (this && this.__importStar) || (function () {
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.STOP_POLL_MAX_MS = exports.STOP_POLL_MIN_MS = exports.DEFAULT_FIX_PROMPT_TEMPLATE = exports.DEFAULT_REPORT_PROMPT_TEMPLATE = exports.DEFAULT_PROMPT_TEMPLATE = exports.NO_BACKGROUND_EXIT_INSTRUCTION = exports.OPENCODE_DISPATCH_COMMAND = exports.DEFAULT_DISPATCH_COMMAND = exports.DEFAULT_TIER_MODELS = void 0;
54
54
  exports.resolveDispatch = resolveDispatch;
55
+ exports.stallTimeoutForPhase = stallTimeoutForPhase;
55
56
  exports.buildAgentCommand = buildAgentCommand;
56
57
  exports.buildPrompt = buildPrompt;
57
58
  exports.buildReportPrompt = buildReportPrompt;
@@ -168,10 +169,50 @@ function resolveDispatch(config) {
168
169
  fixPrompt: dispatch.fix_prompt ?? exports.DEFAULT_FIX_PROMPT_TEMPLATE,
169
170
  tierModels,
170
171
  stallTimeoutMs: config.stall_timeout_ms ?? types_1.DEFAULT_STALL_TIMEOUT_MS,
172
+ phaseStallTimeoutMs: resolvePhaseStallTimeouts(config),
171
173
  reconcileIntervalMs: config.reconcile_interval_ms ?? types_1.DEFAULT_RECONCILE_INTERVAL_MS,
172
174
  prPollIntervalMs: config.pr_poll_interval_ms ?? types_1.DEFAULT_PR_POLL_INTERVAL_MS,
173
175
  };
174
176
  }
177
+ /**
178
+ * Merge the built-in per-phase stall defaults with operator config (#495).
179
+ * A built-in default (`implement`, 90 min) is a FLOOR against the resolved
180
+ * global `stallTimeoutMs`, never silently shortened by it: an operator who
181
+ * already raised the global timeout as a workaround keeps at least that much
182
+ * for `implement` after upgrading. An explicit `dispatch.phase_stall_timeout_ms`
183
+ * entry always wins verbatim — an operator narrowing a phase on purpose is
184
+ * respected, not floored.
185
+ */
186
+ function resolvePhaseStallTimeouts(config) {
187
+ const globalMs = config.stall_timeout_ms ?? types_1.DEFAULT_STALL_TIMEOUT_MS;
188
+ const builtins = Object.fromEntries(Object.entries(types_1.DEFAULT_PHASE_STALL_TIMEOUT_MS).map(([phase, ms]) => [
189
+ phase,
190
+ Math.max(ms, globalMs),
191
+ ]));
192
+ return { ...builtins, ...config.dispatch?.phase_stall_timeout_ms };
193
+ }
194
+ /**
195
+ * The stall timeout to apply for `phase` (#495): a per-phase override (built-
196
+ * in default or operator config, `ResolvedDispatch.phaseStallTimeoutMs`) when
197
+ * one exists for `phase`, else the global `stallTimeoutMs`. `phase` should be
198
+ * the CURRENTLY RUNNING phase — the last milestone's `next=` — not the last
199
+ * COMPLETED phase (`slot.phase`), which lags one phase behind. `next=` may
200
+ * also legally be the terminal sentinel `'done'`, which never matches an
201
+ * override and therefore uses the global timeout.
202
+ *
203
+ * `Object.hasOwn` (not a plain `[]` lookup) guards against `phase` being an
204
+ * `Object.prototype` member name (`toString`, `constructor`, …): `next=` is
205
+ * parsed from a GitHub issue comment, which this repo's own threat model
206
+ * treats as untrusted (anyone who can comment can post a milestone-shaped
207
+ * comment), so an attacker-chosen `next=toString` must not resolve to a
208
+ * function and silently disable the stall check via a `NaN` comparison.
209
+ */
210
+ function stallTimeoutForPhase(dispatch, phase) {
211
+ if (phase !== null && Object.hasOwn(dispatch.phaseStallTimeoutMs, phase)) {
212
+ return dispatch.phaseStallTimeoutMs[phase];
213
+ }
214
+ return dispatch.stallTimeoutMs;
215
+ }
175
216
  /**
176
217
  * Build the concrete agent argv for one dispatch: substitute `{issue}` and
177
218
  * `{model}`. When the tier's model is null, the `{model}` item AND its
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -15,8 +15,10 @@
15
15
  * - `assigned` slots (crash between assign and spawn) are spawned/re-attached
16
16
  * - `running` slots: dead pid → exit rail; ground truth says complete →
17
17
  * external advance (kill the leftover agent, complete); new milestone or
18
- * pushed commit → progress; no progress for `stall_timeout_ms`
19
- * redispatch one tier stronger (cap 2, then failed)
18
+ * pushed commit → progress; no progress for the in-flight phase's
19
+ * stall timeout (`stall_timeout_ms`, or a per-phase override
20
+ * `implement` defaults to 90 min, #495) → redispatch one tier
21
+ * stronger (cap 2, then failed)
20
22
  * - `exited`/`verifying` slots: the agent exited — completion is verified
21
23
  * against ground truth, never assumed (AC2); an exit whose milestone is
22
24
  * the ship phase's `awaiting-merge` (with `pr=`) is a VERIFIED park:
@@ -78,6 +80,10 @@ export interface TickResult {
78
80
  parked: string[];
79
81
  /** Parked units whose merge was accepted this tick (#468). */
80
82
  mergeAccepted: string[];
83
+ /** `failed reason=auto-merge-blocked` units reconciled to `shipped` this tick after a later merge (#501). */
84
+ staleReconciled: string[];
85
+ /** Dependents released from `blocked reason=dep-failed:<n>` this tick because `<n>` reconciled to `shipped` (#501). */
86
+ dependentsUnblocked: string[];
81
87
  /** Report agents dispatched for merged units this tick (#468). */
82
88
  reportDispatched: string[];
83
89
  /** Merged units whose report could not dispatch — waiting for a free slot (#468). */
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAGH,OAAO,EAQL,KAAK,SAAS,EAIf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAe,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAIxC,OAAO,EAML,KAAK,WAAW,EAMjB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,UAAU;IACzB,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,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,sDAAsD;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gFAAgF;IAChF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2FAA2F;IAC3F,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uEAAuE;IACvE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAyhCD;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,CAyCtE;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,OAAO,EACzB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAcf"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAGH,OAAO,EAQL,KAAK,SAAS,EAKf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAe,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAIxC,OAAO,EAML,KAAK,WAAW,EAMjB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,UAAU;IACzB,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,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,sDAAsD;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gFAAgF;IAChF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6GAA6G;IAC7G,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,uHAAuH;IACvH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2FAA2F;IAC3F,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uEAAuE;IACvE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAqsCD;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,CA0CtE;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,OAAO,EACzB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAcf"}
package/dist/engine.js CHANGED
@@ -16,8 +16,10 @@
16
16
  * - `assigned` slots (crash between assign and spawn) are spawned/re-attached
17
17
  * - `running` slots: dead pid → exit rail; ground truth says complete →
18
18
  * external advance (kill the leftover agent, complete); new milestone or
19
- * pushed commit → progress; no progress for `stall_timeout_ms`
20
- * redispatch one tier stronger (cap 2, then failed)
19
+ * pushed commit → progress; no progress for the in-flight phase's
20
+ * stall timeout (`stall_timeout_ms`, or a per-phase override
21
+ * `implement` defaults to 90 min, #495) → redispatch one tier
22
+ * stronger (cap 2, then failed)
21
23
  * - `exited`/`verifying` slots: the agent exited — completion is verified
22
24
  * against ground truth, never assumed (AC2); an exit whose milestone is
23
25
  * the ship phase's `awaiting-merge` (with `pr=`) is a VERIFIED park:
@@ -101,6 +103,8 @@ function emptyResult() {
101
103
  completed: [],
102
104
  parked: [],
103
105
  mergeAccepted: [],
106
+ staleReconciled: [],
107
+ dependentsUnblocked: [],
104
108
  reportDispatched: [],
105
109
  reportWaiting: 0,
106
110
  teardownDone: [],
@@ -180,25 +184,70 @@ function pollUnits(deps, state) {
180
184
  }
181
185
  return out;
182
186
  }
187
+ /**
188
+ * The `QueueEntry.reason` written when the watcher sees the `auto-merge-blocked`
189
+ * label (#468 AC3) — the ONLY reason #501's stale-failure reconcile is
190
+ * eligible for. Written in `reconcileParked`, read by `pollParkedPrs` and
191
+ * `reconcileStaleFailedParks`; conceptually distinct from (but happens to
192
+ * share the string with) the GitHub label name matched in
193
+ * `groundtruth.ts`'s `parsePrViewJson`.
194
+ */
195
+ const AUTO_MERGE_BLOCKED_REASON = 'auto-merge-blocked';
196
+ /**
197
+ * #501: how long after a unit fails `auto-merge-blocked` its PR stays
198
+ * watched for a late operator re-queue + merge. `failed` entries are never
199
+ * pruned from `state.entries`, and each watched entry costs its own
200
+ * `gh pr view` + `gh issue view` per poll — past this window an abandoned
201
+ * entry is left as a plain terminal failure rather than polled forever.
202
+ */
203
+ const STALE_RECONCILE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
204
+ /**
205
+ * #501: a `failed` entry a later merge can still reconcile — the single
206
+ * definition shared by the poll set (`pollParkedPrs`) and the reconcile set
207
+ * (`reconcileStaleFailedParks`, including its mid-loop re-check). Duplicating
208
+ * this predicate is exactly how a poll set and a reconcile set can silently
209
+ * drift out of sync.
210
+ */
211
+ function isStaleFailedPark(e, nowMs) {
212
+ return (e.status === 'failed' &&
213
+ e.reason === AUTO_MERGE_BLOCKED_REASON &&
214
+ e.pr !== null &&
215
+ nowMs - Date.parse(e.updated_at) < STALE_RECONCILE_WINDOW_MS);
216
+ }
217
+ /**
218
+ * RFC-0001 §E.4 / #468 AC1's merged half: MERGED with a real merge
219
+ * timestamp. The issue-closed half is checked separately by callers that
220
+ * need to distinguish "merged, not yet closed" (journaled as
221
+ * `pr-watch-waiting`) from "not merged at all".
222
+ */
223
+ function isPrMerged(truth) {
224
+ return truth.state === 'MERGED' && truth.mergedAt !== null;
225
+ }
183
226
  /**
184
227
  * Poll parked PRs on their own cadence (#468 AC1 — every 2–3 min, persisted
185
228
  * `last_pr_poll_at` so a restart honors it). Runs only when parked entries
186
229
  * exist AND the interval elapsed; every subprocess stays outside the lock.
187
230
  * The issue-closed signal rides along — it is the second half of the
188
231
  * merge-acceptance gate and must not be re-queried under the lock.
232
+ *
233
+ * #501: also polls `failed reason=auto-merge-blocked` entries — a unit an
234
+ * operator may have manually re-queued and merged after the engine already
235
+ * marked it terminal. Piggybacking this poll (rather than a second cadence)
236
+ * means `reconcileStaleFailedParks` needs no separate scheduling — it still
237
+ * costs the usual `gh pr view`/`gh issue view` pair per watched entry.
189
238
  */
190
239
  function pollParkedPrs(deps, state, dispatch) {
191
- const parked = state.entries.filter((e) => e.status === 'parked' && e.pr !== null);
192
- if (parked.length === 0)
240
+ const nowMs = deps.now().getTime();
241
+ const watchable = state.entries.filter((e) => (e.status === 'parked' && e.pr !== null) || isStaleFailedPark(e, nowMs));
242
+ if (watchable.length === 0)
193
243
  return { ran: false, truths: new Map(), closed: new Map() };
194
- const now = deps.now().getTime();
195
244
  const last = state.last_pr_poll_at !== null ? Date.parse(state.last_pr_poll_at) : 0;
196
- if (Number.isFinite(last) && now - last < dispatch.prPollIntervalMs) {
245
+ if (Number.isFinite(last) && nowMs - last < dispatch.prPollIntervalMs) {
197
246
  return { ran: false, truths: new Map(), closed: new Map() };
198
247
  }
199
248
  const truths = new Map();
200
249
  const closed = new Map();
201
- for (const entry of parked) {
250
+ for (const entry of watchable) {
202
251
  truths.set(entry.issue, deps.groundTruth.prState(entry.pr));
203
252
  closed.set(entry.issue, deps.groundTruth.issueClosed(entry.issue));
204
253
  }
@@ -581,9 +630,20 @@ function reconcileRunning(ctx, state, slot, truth, unit) {
581
630
  const progress = applyProgressSignals(ctx, state, slot, truth, unit);
582
631
  if (progress.progressed)
583
632
  return progress.state;
584
- // No progress: the stall timer (AC4).
585
- if (msSinceLastProgress(slot, now) >= ctx.dispatch.stallTimeoutMs) {
586
- return enterRecovery(ctx, progress.state, unit, 'stalled', 'stall');
633
+ // No progress: the stall timer (AC4). The phase now IN FLIGHT is the last
634
+ // milestone's `next=`, not `slot.phase` — `slot.phase` is set to
635
+ // `truth.milestone.phase`, which names the phase that just COMPLETED, so
636
+ // using it directly would apply a phase's timeout allowance to the phase
637
+ // AFTER it (#495). Falls back to `slot.phase` (set to 'gate' at spawn,
638
+ // 'report' for a report agent) for the brief window before any milestone
639
+ // has posted.
640
+ const activePhase = truth.milestone?.keys.next ?? slot.phase;
641
+ const stallTimeoutMs = (0, dispatch_1.stallTimeoutForPhase)(ctx.dispatch, activePhase);
642
+ if (msSinceLastProgress(slot, now) >= stallTimeoutMs) {
643
+ return enterRecovery(ctx, progress.state, unit, 'stalled', 'stall', {
644
+ active_phase: activePhase,
645
+ stall_timeout_ms: stallTimeoutMs,
646
+ });
587
647
  }
588
648
  return progress.state;
589
649
  }
@@ -789,19 +849,15 @@ function reconcileParked(ctx, state, prPoll) {
789
849
  journal(ctx, 'pr-watch-failed', unit, { reason, pr: entry.pr });
790
850
  return failUnit(ctx, next, unit, reason);
791
851
  };
792
- if (truth.blocked) {
793
- next = failWatch('auto-merge-blocked');
794
- continue;
795
- }
796
- if (truth.mergeable === 'CONFLICTING') {
797
- next = failWatch('pr-conflicting');
798
- continue;
799
- }
800
- if (truth.state === 'CLOSED' && truth.mergedAt === null) {
801
- next = failWatch('pr-closed-unmerged');
802
- continue;
803
- }
804
- if (truth.state === 'MERGED' && truth.mergedAt !== null) {
852
+ // #501: MERGED is checked FIRST, before any failure rail. A PR that is
853
+ // genuinely merged is merged regardless of a leftover `auto-merge-blocked`
854
+ // label (GitHub does not clear labels on merge) or a `mergeable` snapshot
855
+ // that hasn't caught up yet — checking `blocked`/`CONFLICTING` first would
856
+ // fail a unit whose work already shipped, only to have
857
+ // `reconcileStaleFailedParks` immediately re-reconcile it later that same
858
+ // tick, after `blockTransitiveDependents` had already wedged its
859
+ // dependents on a "failure" that was never real.
860
+ if (isPrMerged(truth)) {
805
861
  // AC1: the issue must ALSO be closed (a merged PR auto-closes it) —
806
862
  // until GitHub propagates, the unit stays parked and keeps watching.
807
863
  if (prPoll.closed.get(issue) !== true) {
@@ -815,11 +871,114 @@ function reconcileParked(ctx, state, prPoll) {
815
871
  next = (0, state_1.transitionIssue)(next, issue, 'shipped', { reason: null }, ctx.deps.now());
816
872
  journal(ctx, 'merge-accepted', unit, { pr: entry.pr, mergedAt: truth.mergedAt });
817
873
  ctx.result.mergeAccepted.push(unit);
874
+ continue;
875
+ }
876
+ if (truth.blocked) {
877
+ next = failWatch(AUTO_MERGE_BLOCKED_REASON);
878
+ continue;
879
+ }
880
+ if (truth.mergeable === 'CONFLICTING') {
881
+ next = failWatch('pr-conflicting');
882
+ continue;
883
+ }
884
+ if (truth.state === 'CLOSED' && truth.mergedAt === null) {
885
+ next = failWatch('pr-closed-unmerged');
818
886
  }
819
- // OPEN (or MERGED without a date / mergeable UNKNOWN) — keep watching.
887
+ // OPEN (or mergeable UNKNOWN) — keep watching.
820
888
  }
821
889
  return next;
822
890
  }
891
+ /**
892
+ * #501: reconcile `failed reason=auto-merge-blocked` entries whose PR later
893
+ * merged after an operator manually re-queued it (removed
894
+ * `auto-merge-blocked`, re-added `auto-merge`) — outside the engine's own
895
+ * watch, since `reconcileParked` stops watching an entry the instant it
896
+ * leaves `parked`, including into `failed`. Flips it to `shipped` so
897
+ * `sched status` and `dispatchReportAgents` treat it exactly like a
898
+ * normally-watched merge, and unblocks dependents wedged on the original
899
+ * (now-reversed) failure; ground truth comes from `pollParkedPrs`, which
900
+ * this piggybacks — no separate poll pass, though each watched entry still
901
+ * costs its own `gh pr view`/`gh issue view`.
902
+ *
903
+ * Deliberately narrow: only `isStaleFailedPark` entries are eligible (AC3) —
904
+ * a `failed` entry for any other reason, or one whose PR has sat blocked
905
+ * past `STALE_RECONCILE_WINDOW_MS`, is never touched.
906
+ */
907
+ function reconcileStaleFailedParks(ctx, state, prPoll) {
908
+ if (!prPoll.ran)
909
+ return state;
910
+ let next = state;
911
+ const nowMs = ctx.deps.now().getTime();
912
+ const staleFailed = state.entries.filter((e) => isStaleFailedPark(e, nowMs)).map((e) => e.issue);
913
+ for (const issue of staleFailed) {
914
+ const entry = (0, state_1.findEntry)(next, issue);
915
+ if (!entry || !isStaleFailedPark(entry, nowMs))
916
+ continue; // reconciled or expired mid-loop
917
+ const unit = `issue:${issue}`;
918
+ const truth = prPoll.truths.get(issue);
919
+ if (truth === undefined) {
920
+ if (!prPoll.truths.has(issue)) {
921
+ continue; // failed AFTER the poll ran this tick — next cadence picks it up
922
+ }
923
+ journal(ctx, 'ground-truth-unreachable', unit, {
924
+ detail: 'stale-failure reconcile paused until truth returns',
925
+ });
926
+ continue;
927
+ }
928
+ if (!isPrMerged(truth))
929
+ continue; // still blocked/open/conflicting — stays failed
930
+ if (prPoll.closed.get(issue) !== true) {
931
+ journal(ctx, 'pr-watch-waiting', unit, {
932
+ pr: entry.pr,
933
+ mergedAt: truth.mergedAt,
934
+ reason: AUTO_MERGE_BLOCKED_REASON,
935
+ detail: 'stale-failed PR merged but the issue is still open — keep watching',
936
+ });
937
+ continue;
938
+ }
939
+ const failedAt = entry.updated_at;
940
+ next = (0, state_1.transitionIssue)(next, issue, 'shipped', { reason: null }, ctx.deps.now());
941
+ journal(ctx, 'stale-failure-reconciled', unit, {
942
+ pr: entry.pr,
943
+ mergedAt: truth.mergedAt,
944
+ reason: AUTO_MERGE_BLOCKED_REASON,
945
+ failedAt,
946
+ detail: `PR #${entry.pr} is MERGED and the issue is closed — ledger reconciled failed to shipped (failed at ${failedAt}); teardown and report will now dispatch`,
947
+ });
948
+ ctx.result.staleReconciled.push(unit);
949
+ const unblocked = unblockDependentsOf(ctx, next, issue);
950
+ next = unblocked.state;
951
+ ctx.result.dependentsUnblocked.push(...unblocked.units);
952
+ }
953
+ return next;
954
+ }
955
+ /**
956
+ * #501: undo `blockTransitiveDependents` for a unit that turned out to have
957
+ * shipped after all. `blockTransitiveDependents` stamps EVERY entry in the
958
+ * transitive closure with the same `dep-failed:<issue>` reason (never a
959
+ * more-specific one per level), so matching that one string recovers the
960
+ * whole closure without re-walking the dependency graph. A `slot`-mode
961
+ * dependent returns to `waiting` (its batch rail), a `full`-mode one to
962
+ * `queued` — both are already-legal edges out of `blocked`.
963
+ */
964
+ function unblockDependentsOf(ctx, state, issue) {
965
+ const depReason = `dep-failed:${issue}`;
966
+ const now = ctx.deps.now();
967
+ let next = state;
968
+ const units = [];
969
+ for (const entry of state.entries) {
970
+ if (entry.status !== 'blocked' || entry.reason !== depReason)
971
+ continue;
972
+ const to = entry.mode === 'slot' ? 'waiting' : 'queued';
973
+ next = (0, state_1.transitionIssue)(next, entry.issue, to, { reason: null }, now);
974
+ const unit = `issue:${entry.issue}`;
975
+ journal(ctx, 'stale-failure-reconciled', unit, {
976
+ detail: `dependency #${issue} actually merged — unblocked from ${depReason}`,
977
+ });
978
+ units.push(unit);
979
+ }
980
+ return { state: next, units };
981
+ }
823
982
  /**
824
983
  * Dispatch report agents for merged units whose teardown is recorded (#468
825
984
  * AC2): shipped + pr + cleanup + no live slot + free capacity → a slot is
@@ -957,6 +1116,7 @@ function tick(deps, config) {
957
1116
  const ctx = { deps, dispatch, result: emptyResult() };
958
1117
  let next = reconcileSlots(ctx, state, polled);
959
1118
  next = reconcileParked(ctx, next, prPoll);
1119
+ next = reconcileStaleFailedParks(ctx, next, prPoll);
960
1120
  next = requeueOrphanedDispatches(ctx, next);
961
1121
  next = dispatchReportAgents(ctx, next, config);
962
1122
  next = dispatchAssignments(ctx, next, config);