@ai-dossier/sched 0.58.0 → 0.58.1
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/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +22 -0
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +125 -24
- package/dist/engine.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +7 -0
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAUH,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,kBAAkB,CAAC;AAE3E,OAAO,EAcL,KAAK,SAAS,EAKf,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -111,6 +111,7 @@ const core_1 = require("@ai-dossier/core");
|
|
|
111
111
|
const batch_dispatch_1 = require("./batch-dispatch");
|
|
112
112
|
const dispatch_1 = require("./dispatch");
|
|
113
113
|
const dispatch_health_1 = require("./dispatch-health");
|
|
114
|
+
const dossier_root_1 = require("./dossier-root");
|
|
114
115
|
const fence_1 = require("./fence");
|
|
115
116
|
const groundtruth_1 = require("./groundtruth");
|
|
116
117
|
const journal_1 = require("./journal");
|
|
@@ -237,6 +238,28 @@ function pollUnits(deps, state) {
|
|
|
237
238
|
});
|
|
238
239
|
continue;
|
|
239
240
|
}
|
|
241
|
+
if (slot.status === 'assigned') {
|
|
242
|
+
// #778: a crash-left `assigned` slot is spawned fresh by
|
|
243
|
+
// `reconcileAssigned` — the same "resume after days" shape as
|
|
244
|
+
// `recovering`. A live pid is re-attached, never re-spawned, so it needs
|
|
245
|
+
// no read; report slots close their issue at merge, by design.
|
|
246
|
+
const issue = (0, journal_1.issueOfUnit)(slot.unit);
|
|
247
|
+
if (issue === null || out.has(slot.unit) || (0, state_1.isReportSlot)(slot))
|
|
248
|
+
continue;
|
|
249
|
+
if (slot.pid !== null && deps.spawnDeps.isAlive(slot.pid, slot.pid_start ?? undefined)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (((0, state_1.findEntry)(state, issue)?.stale_closed_at ?? null) !== null)
|
|
253
|
+
continue;
|
|
254
|
+
out.set(slot.unit, {
|
|
255
|
+
reachable: true,
|
|
256
|
+
milestone: null,
|
|
257
|
+
closed: deps.groundTruth.issueClosed(issue),
|
|
258
|
+
head: null,
|
|
259
|
+
branch: slot.branch,
|
|
260
|
+
});
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
240
263
|
if (slot.status !== 'running' && slot.status !== 'verifying' && slot.status !== 'exited') {
|
|
241
264
|
continue;
|
|
242
265
|
}
|
|
@@ -396,6 +419,36 @@ function pollLabels(deps, state, config, dispatch) {
|
|
|
396
419
|
labels.set(issue, deps.groundTruth.issueLabels(issue));
|
|
397
420
|
return { ran: true, labels };
|
|
398
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* #778: one `issueClosed` read per issue unit the dispatch pass could place
|
|
424
|
+
* this tick (the first `max_slots` runnable, unheld, unflagged issue units —
|
|
425
|
+
* the same ceiling as `pollLabels`) plus orphaned `dispatched` entries that
|
|
426
|
+
* `requeueOrphanedDispatches` is about to put back in that queue. A paused
|
|
427
|
+
* fleet dispatches nothing, so it reads nothing. `issueClosed` is false when
|
|
428
|
+
* gh is unreachable: an unreadable issue is never treated as closed.
|
|
429
|
+
*/
|
|
430
|
+
function pollClosed(deps, state, config) {
|
|
431
|
+
const out = new Map();
|
|
432
|
+
if (state.paused)
|
|
433
|
+
return out;
|
|
434
|
+
const held = new Set(state.slots.map((slot) => slot.unit).filter((u) => u !== null));
|
|
435
|
+
const unflagged = (issue) => (0, state_1.findEntry)(state, issue)?.stale_closed_at === null;
|
|
436
|
+
const candidates = state.entries
|
|
437
|
+
.filter((e) => e.status === 'dispatched' && !held.has(`issue:${e.issue}`))
|
|
438
|
+
.map((e) => e.issue);
|
|
439
|
+
for (const unit of (0, readiness_1.runnableUnits)(state)) {
|
|
440
|
+
if (unit.kind === 'issue' && !held.has(`issue:${unit.issue}`))
|
|
441
|
+
candidates.push(unit.issue);
|
|
442
|
+
}
|
|
443
|
+
for (const issue of candidates) {
|
|
444
|
+
if (out.size >= config.max_slots)
|
|
445
|
+
break;
|
|
446
|
+
if (out.has(issue) || !unflagged(issue))
|
|
447
|
+
continue;
|
|
448
|
+
out.set(issue, deps.groundTruth.issueClosed(issue));
|
|
449
|
+
}
|
|
450
|
+
return out;
|
|
451
|
+
}
|
|
399
452
|
/**
|
|
400
453
|
* Issues this tick's read CONFIRMED carry no hard-block label (#544) — the
|
|
401
454
|
* only issues `dispatchAssignments` may place. A failed read is absent here,
|
|
@@ -655,7 +708,9 @@ function seedResumeTrail(ctx, state, issue) {
|
|
|
655
708
|
branch,
|
|
656
709
|
baseBranch,
|
|
657
710
|
batch: evidence.batch,
|
|
658
|
-
|
|
711
|
+
// #890: the same project-root resolution batch worktrees use, so a nested
|
|
712
|
+
// layout (`.dossier/` above the checkout) lands slot + batch trees in one root.
|
|
713
|
+
worktree: path.join((0, dossier_root_1.worktreesDirFor)(ctx.deps.repoDir), branch.replaceAll('/', '-')),
|
|
659
714
|
revertedCommits: evidence.reverted_commits,
|
|
660
715
|
});
|
|
661
716
|
if (!outcome.ok) {
|
|
@@ -2019,8 +2074,36 @@ dispatchSignals = NO_DISPATCH_SIGNALS) {
|
|
|
2019
2074
|
...(resolved.lastTool !== null ? { last_tool: resolved.lastTool } : {}),
|
|
2020
2075
|
}, { escalate: progressed });
|
|
2021
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* The stale-closed guard shared by every slot rail that would spawn a fresh
|
|
2079
|
+
* agent (#776 `recovering`, #778 `assigned`). Returns the state to keep (the
|
|
2080
|
+
* unit must NOT be spawned this tick) or `null` when the spawn may proceed.
|
|
2081
|
+
*
|
|
2082
|
+
* The flag is sticky and the slot stays held: releasing it is an operator
|
|
2083
|
+
* decision (`sched stop --issue N`), which `sched status` names. `truth.closed`
|
|
2084
|
+
* is false when gh is unreachable, so an unreachable poll never flags a unit
|
|
2085
|
+
* (no work is killed on a network blip) — and an already-flagged unit stays
|
|
2086
|
+
* held without any read, so a blip cannot un-hold it either.
|
|
2087
|
+
*/
|
|
2088
|
+
function holdIfIssueClosed(ctx, state, slot, unit, truth) {
|
|
2089
|
+
const issue = (0, journal_1.issueOfUnit)(unit);
|
|
2090
|
+
const entry = issue === null ? undefined : (0, state_1.findEntry)(state, issue);
|
|
2091
|
+
if (!entry || issue === null || (0, state_1.isReportSlot)(slot))
|
|
2092
|
+
return null;
|
|
2093
|
+
if (entry.stale_closed_at !== null)
|
|
2094
|
+
return state;
|
|
2095
|
+
if (!truth.closed)
|
|
2096
|
+
return null;
|
|
2097
|
+
const now = ctx.deps.now();
|
|
2098
|
+
journal(ctx, 'stale-closed', unit, {
|
|
2099
|
+
slot: slot.id,
|
|
2100
|
+
detail: `issue #${issue} is closed — recovery will not re-dispatch it; release the slot with \`sched stop --issue ${issue}\``,
|
|
2101
|
+
});
|
|
2102
|
+
// Not an activity bump: `updated_at` keeps meaning "last state change".
|
|
2103
|
+
return (0, state_1.patchEntry)(state, issue, { stale_closed_at: now.toISOString() }, now, false);
|
|
2104
|
+
}
|
|
2022
2105
|
/** Re-attach or spawn a slot left `assigned` by a crash between assign and spawn. */
|
|
2023
|
-
function reconcileAssigned(ctx, state, slot, unit) {
|
|
2106
|
+
function reconcileAssigned(ctx, state, slot, unit, truth) {
|
|
2024
2107
|
if (slot.pid !== null && ctx.deps.spawnDeps.isAlive(slot.pid)) {
|
|
2025
2108
|
// Crash after spawn, before the running transition: re-attach by pid.
|
|
2026
2109
|
journal(ctx, 'orphan-pid', unit, {
|
|
@@ -2030,6 +2113,11 @@ function reconcileAssigned(ctx, state, slot, unit) {
|
|
|
2030
2113
|
});
|
|
2031
2114
|
return (0, state_1.transitionSlot)(state, slot.id, 'running', {}, ctx.deps.now());
|
|
2032
2115
|
}
|
|
2116
|
+
// #778: never spawn fresh for an issue that is closed on GitHub. Same model
|
|
2117
|
+
// as `reconcileRecovering` (#776): flag, journal, hold the slot.
|
|
2118
|
+
const held = holdIfIssueClosed(ctx, state, slot, unit, truth);
|
|
2119
|
+
if (held !== null)
|
|
2120
|
+
return held;
|
|
2033
2121
|
if (state.paused)
|
|
2034
2122
|
return state;
|
|
2035
2123
|
journal(ctx, 'assigned', unit, { slot: slot.id, detail: 'crash-recovery spawn' });
|
|
@@ -2040,24 +2128,10 @@ function reconcileRecovering(ctx, state, slot, truth, unit) {
|
|
|
2040
2128
|
// #776: never re-dispatch a unit whose issue is closed. Checked BEFORE the
|
|
2041
2129
|
// pause guard so a paused scheduler still flags it (the incident shape:
|
|
2042
2130
|
// paused for days with a recovering slot on an issue shipped elsewhere —
|
|
2043
|
-
// `sched resume` would have re-run finished work).
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const entry = issue === null ? undefined : (0, state_1.findEntry)(state, issue);
|
|
2048
|
-
if (entry && issue !== null && !(0, state_1.isReportSlot)(slot)) {
|
|
2049
|
-
if (entry.stale_closed_at !== null)
|
|
2050
|
-
return state;
|
|
2051
|
-
if (truth.closed) {
|
|
2052
|
-
const now = ctx.deps.now();
|
|
2053
|
-
journal(ctx, 'stale-closed', unit, {
|
|
2054
|
-
slot: slot.id,
|
|
2055
|
-
detail: `issue #${issue} is closed — recovery will not re-dispatch it; release the slot with \`sched stop --issue ${issue}\``,
|
|
2056
|
-
});
|
|
2057
|
-
// Not an activity bump: `updated_at` keeps meaning "last state change".
|
|
2058
|
-
return (0, state_1.patchEntry)(state, issue, { stale_closed_at: now.toISOString() }, now, false);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2131
|
+
// `sched resume` would have re-run finished work).
|
|
2132
|
+
const held = holdIfIssueClosed(ctx, state, slot, unit, truth);
|
|
2133
|
+
if (held !== null)
|
|
2134
|
+
return held;
|
|
2061
2135
|
// #629: while paused, do not resume a `recovering` slot's respawn — this is
|
|
2062
2136
|
// the crash-recovery rail (a sched restart caught a slot between
|
|
2063
2137
|
// `enterRecovery`'s transition and its own `spawnUnit` call, OR `enterRecovery`
|
|
@@ -2096,7 +2170,7 @@ function reconcileSlots(ctx, state, polled) {
|
|
|
2096
2170
|
};
|
|
2097
2171
|
switch (slot.status) {
|
|
2098
2172
|
case 'assigned':
|
|
2099
|
-
next = reconcileAssigned(ctx, next, slot, unit);
|
|
2173
|
+
next = reconcileAssigned(ctx, next, slot, unit, truth);
|
|
2100
2174
|
break;
|
|
2101
2175
|
case 'running':
|
|
2102
2176
|
next = reconcileRunning(ctx, next, slot, truth, unit);
|
|
@@ -2137,7 +2211,14 @@ function requeueOrphanedDispatches(ctx, state) {
|
|
|
2137
2211
|
// that condition's first occurrence on this one. Without it, a crash
|
|
2138
2212
|
// during an outage leaves `ground_truth_unreachable_ticks` mid-streak and
|
|
2139
2213
|
// the redispatched unit stays silent until the next re-announcement.
|
|
2140
|
-
next = (0, state_1.transitionIssue)(next, entry.issue, 'queued', {
|
|
2214
|
+
next = (0, state_1.transitionIssue)(next, entry.issue, 'queued', {
|
|
2215
|
+
reason: null,
|
|
2216
|
+
...state_1.CLEARED_ENTRY_DEDUP_MARKERS,
|
|
2217
|
+
// #778: a stale-closed flag survives the requeue — clearing it would
|
|
2218
|
+
// let one unreachable-gh tick re-dispatch a shipped issue. Unflagged
|
|
2219
|
+
// orphans are read by `pollClosed` and flagged in the dispatch pass.
|
|
2220
|
+
stale_closed_at: entry.stale_closed_at,
|
|
2221
|
+
}, now);
|
|
2141
2222
|
journal(ctx, 'requeued', `issue:${entry.issue}`, {
|
|
2142
2223
|
detail: 'orphaned dispatch requeued after restart',
|
|
2143
2224
|
});
|
|
@@ -2401,7 +2482,7 @@ function runTeardownFor(deps, issue) {
|
|
|
2401
2482
|
return (0, teardown_1.runTeardown)(deps.teardownExec, deps.repoDir, info);
|
|
2402
2483
|
}
|
|
2403
2484
|
/** Phase 3: refill — every freed slot is filled in THIS tick (AC5). */
|
|
2404
|
-
function dispatchAssignments(ctx, state, config, labelVerifiedIssues) {
|
|
2485
|
+
function dispatchAssignments(ctx, state, config, labelVerifiedIssues, closedPoll) {
|
|
2405
2486
|
const now = ctx.deps.now();
|
|
2406
2487
|
// #544: `pollLabels` reads only the first `max_slots` runnable units — the
|
|
2407
2488
|
// ceiling on what this pass can place — which keeps the per-tick `gh` cost
|
|
@@ -2420,6 +2501,12 @@ function dispatchAssignments(ctx, state, config, labelVerifiedIssues) {
|
|
|
2420
2501
|
.filter((entry) => !labelVerifiedIssues.has(entry.issue))
|
|
2421
2502
|
.map((entry) => `issue:${entry.issue}`))
|
|
2422
2503
|
: new Set();
|
|
2504
|
+
// #778: a stale-closed entry is never dispatched (the flag is sticky; only
|
|
2505
|
+
// `sched stop` / a requeue-elsewhere resolves it).
|
|
2506
|
+
for (const entry of state.entries) {
|
|
2507
|
+
if (entry.stale_closed_at !== null)
|
|
2508
|
+
exclude.add(`issue:${entry.issue}`);
|
|
2509
|
+
}
|
|
2423
2510
|
// #565: decide who wins each free slot over BOTH kinds — a ready batch
|
|
2424
2511
|
// outranking a same-readiness issue (priority desc → readiness age → issue
|
|
2425
2512
|
// number, `runnableUnits`) must not lose its capacity to an issue dispatch
|
|
@@ -2451,6 +2538,19 @@ function dispatchAssignments(ctx, state, config, labelVerifiedIssues) {
|
|
|
2451
2538
|
const entry = (0, state_1.findEntry)(next, assignment.issue);
|
|
2452
2539
|
if (!entry)
|
|
2453
2540
|
continue;
|
|
2541
|
+
// #778: queued/classified entries (and requeued orphans) whose issue was
|
|
2542
|
+
// closed while they waited are flagged, not dispatched.
|
|
2543
|
+
// Unread (unblocked mid-tick by the label pass, or slid into the window)
|
|
2544
|
+
// falls back to one bounded read here: assignments never exceed free
|
|
2545
|
+
// capacity, and a same-tick refill (#525 AC5) must not be deferred.
|
|
2546
|
+
const closed = closedPoll.get(assignment.issue) ?? ctx.deps.groundTruth.issueClosed(assignment.issue);
|
|
2547
|
+
if (closed) {
|
|
2548
|
+
journal(ctx, 'stale-closed', unit, {
|
|
2549
|
+
detail: `issue #${assignment.issue} is closed — will not dispatch it; drop it with \`sched stop --issue ${assignment.issue}\``,
|
|
2550
|
+
});
|
|
2551
|
+
next = (0, state_1.patchEntry)(next, assignment.issue, { stale_closed_at: now.toISOString() }, now, false);
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2454
2554
|
const { state: withSlot, slotId } = (0, scheduler_1.assignToIdleSlot)(next, unit, null, now);
|
|
2455
2555
|
next = withSlot;
|
|
2456
2556
|
journal(ctx, 'assigned', unit, { slot: slotId, priority: entry.priority });
|
|
@@ -2504,6 +2604,7 @@ function tick(deps, config) {
|
|
|
2504
2604
|
const prPoll = pollParkedPrs(deps, state0, dispatch);
|
|
2505
2605
|
const labelPoll = pollLabels(deps, state0, config, dispatch);
|
|
2506
2606
|
const labelVerifiedIssues = labelVerified(labelPoll);
|
|
2607
|
+
const closedPoll = pollClosed(deps, state0, config);
|
|
2507
2608
|
const pass1 = deps.store.withLock((state) => {
|
|
2508
2609
|
const ctx = { deps, config, dispatch, result: emptyResult() };
|
|
2509
2610
|
let next = reconcileSlots(ctx, state, polled);
|
|
@@ -2514,7 +2615,7 @@ function tick(deps, config) {
|
|
|
2514
2615
|
// #544: immediately before the dispatch pass, so a cleared label can be
|
|
2515
2616
|
// dispatched this same tick and a fresh one can never be dispatched over.
|
|
2516
2617
|
next = reconcileLabelBlocks(ctx, next, labelPoll);
|
|
2517
|
-
next = dispatchAssignments(ctx, next, config, labelVerifiedIssues);
|
|
2618
|
+
next = dispatchAssignments(ctx, next, config, labelVerifiedIssues, closedPoll);
|
|
2518
2619
|
return {
|
|
2519
2620
|
state: next,
|
|
2520
2621
|
result: { tick: ctx.result, teardownPending: teardownPendingIssues(next) },
|