@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 +39 -5
- package/dist/dispatch.d.ts +26 -0
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +41 -0
- package/dist/dispatch.js.map +1 -1
- package/dist/engine.d.ts +8 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +184 -24
- 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 -3
- package/dist/index.js.map +1 -1
- package/dist/persist.d.ts.map +1 -1
- package/dist/persist.js +47 -22
- package/dist/persist.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +15 -1
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +68 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +37 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
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/
|
|
226
|
-
// teardown/completed/
|
|
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
|
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -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
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
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
|
package/dist/dispatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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
|
|
19
|
-
*
|
|
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). */
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA
|
|
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
|
|
20
|
-
*
|
|
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
|
|
192
|
-
|
|
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) &&
|
|
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
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if (truth
|
|
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
|
|
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);
|