@ai-dossier/sched 0.44.0 → 0.46.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.
Files changed (50) hide show
  1. package/README.md +69 -2
  2. package/dist/anchor-close.d.ts +172 -0
  3. package/dist/anchor-close.d.ts.map +1 -0
  4. package/dist/anchor-close.js +348 -0
  5. package/dist/anchor-close.js.map +1 -0
  6. package/dist/batch-dispatch.d.ts +16 -3
  7. package/dist/batch-dispatch.d.ts.map +1 -1
  8. package/dist/batch-dispatch.js +225 -18
  9. package/dist/batch-dispatch.js.map +1 -1
  10. package/dist/batch-stats.d.ts +23 -0
  11. package/dist/batch-stats.d.ts.map +1 -1
  12. package/dist/batch-stats.js +81 -6
  13. package/dist/batch-stats.js.map +1 -1
  14. package/dist/engine.d.ts +2 -0
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +1 -0
  17. package/dist/engine.js.map +1 -1
  18. package/dist/groundtruth.d.ts +86 -0
  19. package/dist/groundtruth.d.ts.map +1 -1
  20. package/dist/groundtruth.js +128 -2
  21. package/dist/groundtruth.js.map +1 -1
  22. package/dist/index.d.ts +6 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +27 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/labels.d.ts +8 -0
  27. package/dist/labels.d.ts.map +1 -1
  28. package/dist/labels.js +14 -2
  29. package/dist/labels.js.map +1 -1
  30. package/dist/project.d.ts +10 -0
  31. package/dist/project.d.ts.map +1 -1
  32. package/dist/project.js +27 -0
  33. package/dist/project.js.map +1 -1
  34. package/dist/recovery.d.ts +11 -0
  35. package/dist/recovery.d.ts.map +1 -1
  36. package/dist/recovery.js +1 -1
  37. package/dist/recovery.js.map +1 -1
  38. package/dist/state.d.ts +12 -0
  39. package/dist/state.d.ts.map +1 -1
  40. package/dist/state.js +39 -4
  41. package/dist/state.js.map +1 -1
  42. package/dist/status.d.ts +16 -1
  43. package/dist/status.d.ts.map +1 -1
  44. package/dist/status.js +10 -1
  45. package/dist/status.js.map +1 -1
  46. package/dist/types.d.ts +26 -3
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js +5 -1
  49. package/dist/types.js.map +1 -1
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ ai-dossier sched enqueue --issues 101,105..109 --deps 100 --tier strong # flag
28
28
  ai-dossier sched enqueue --from-manifest batch-prep.json # batch-prep output
29
29
  ai-dossier sched start # the dispatch engine: spawn, verify, escalate, watch parked PRs (Ctrl-C stops it)
30
30
  ai-dossier sched start --once # a single reconcile+refill tick (cron-style)
31
- ai-dossier sched status # queue (+pr/cleanup), engine lease, slots, batches, blocked/failed
31
+ ai-dossier sched status # queue (+pr/cleanup), engine lease, slots, batches, blocked/failed; --anchors adds the open-batch-anchor sweep (#768)
32
32
  ai-dossier sched pause # prevent every new agent process; live units keep running
33
33
  ai-dossier sched resume
34
34
  ai-dossier sched stop --issue 42 # terminate one full-cycle agent and record it stopped (no recovery)
@@ -730,6 +730,17 @@ row governs assignment), so the Queue table renders `-` for it rather than a num
730
730
  looks load-bearing but is not. A batch dissolve (`recovery.ts`'s `dissolveBatch`) carries
731
731
  the parent batch's priority forward onto both split halves.
732
732
 
733
+ ## The batch gate — `gate.batch` (#777)
734
+
735
+ `runValidate` runs the injected `runSuite(worktree, ctx)` once per `validating` pass;
736
+ `ctx` is `{ batchId, baseRef: 'origin/<base_branch>' }` (`BatchSuiteContext`, also passed
737
+ on attribution/fix re-runs). The CLI's runner (`cli/src/batch-suite-runner.ts`) prefers the
738
+ repo's active `gate.batch` capability — the same CI-parity gate a normal PR pays — over
739
+ `test.full`, exporting `ctx` as `DOSSIER_BATCH_ID` / `DOSSIER_BATCH_BASE`; outcomes map
740
+ identically either way (`ok` / red-and-attributable / `suite-unreadable` block).
741
+ `sched enqueue` refuses to form a batch whose only full gate is a `test.full` marked
742
+ `timeout_prone: true`. Details: `docs/reference/capabilities.md`.
743
+
733
744
  ## Batch dispatch (#523)
734
745
 
735
746
  `batch-dispatch.ts`'s `runBatchTick` — called from `tick()` after the issue-level pass,
@@ -1023,6 +1034,14 @@ import {
1023
1034
  // so batch-dispatch.ts can share it.
1024
1035
  TRANSITIONS, // the transition tables themselves (for previews)
1025
1036
  buildStatusReport, // machine-readable status incl. blocked/failed sets
1037
+ // (+ optional `anchorSweep` → `anchors`, #768; null without it)
1038
+ sweepAnchors, classifyAnchor, membersShippedVerdict, anchorLedgerBlockers,
1039
+ shippingEvidence, // #768 anchor-close predicate (pure; readers injected)
1040
+ closeAnchor, // #768 idempotent comment-then-close, `-R <repo>` explicit
1041
+ anchorCloseMarker, renderAnchorCloseComment, openAnchorBatches, formatBatchStatus,
1042
+ issueCloseReader, parseIssueCloseTruthJson, parseRepoName,
1043
+ resolveProjectRepo, // #768 owner/name of the cwd repo only when it IS the project's
1044
+ hasLabel, // case-insensitive label match (#768)
1026
1045
  validateState, // strict persisted-state validation (1.0.0-1.13.0 files migrate)
1027
1046
  DEFAULT_ISSUE_PRIORITY, DEFAULT_BATCH_PRIORITY, // priority defaults (0 / 10, #565)
1028
1047
  IllegalTransitionError, EnqueueError, CorruptStateError, LockTimeoutError,
@@ -1202,7 +1221,8 @@ after-the-fact recovery, not a missing-data bug.
1202
1221
  `member_gates`/`blocked_reason` — #565/#583, slot
1203
1222
  `stale_milestone_ignored_for` backfill to `null` — #610, and state-level
1204
1223
  `consecutive_dispatch_api_errors`/`dispatch_pause_reset_at` backfill to `0`/`null` —
1205
- #629).
1224
+ #629; batch `anchor_closed_at` and `anchor_close_failed_reason`/`_since`/`_ticks`
1225
+ backfill to `null`/`null`/`null`/`0` — #768, schema 1.22.0).
1206
1226
  - **`max_slots`** bounds live units (`assigned | running | recovering`); dependency
1207
1227
  edges gate readiness — an issue with an unmerged dependency, and a batch behind an
1208
1228
  unmerged batch, are never runnable.
@@ -1234,6 +1254,53 @@ nobody came back to, each with its exact remedy:
1234
1254
  the recovery rail never respawns it (even after `sched resume`). Report slots are exempt:
1235
1255
  their issue is closed at merge by design. Remedy: `sched stop --issue N`.
1236
1256
 
1257
+ ## Batch anchors close only on positive evidence (#768)
1258
+
1259
+ A batch's anchor issue used to close only on the happy path (batch-integrate's
1260
+ post-merge step, #720). A batch that blocked, was recovered by hand, or whose members
1261
+ shipped through PRs of their own left its anchor open forever (imboard#4244,
1262
+ imboard#4253). Each tick now runs `reconcileAnchorClosure` (`batch-dispatch.ts`, with the
1263
+ predicate in `anchor-close.ts`) over `blocked`/`done` batches touched within the 7-day
1264
+ `STALE_BLOCKED_RECONCILE_WINDOW_MS` whose anchor is not yet recorded closed:
1265
+
1266
+ - **Closes only when every member shipped.** Each member issue must be CLOSED with
1267
+ `stateReason=COMPLETED`, and its close event's closer must be a PR of the project's own repository MERGED
1268
+ into the batch's `base_branch` or a commit reachable from `origin/<base>`
1269
+ (`shippingEvidence`). A hand close — no linked PR or commit — is not evidence: an
1270
+ issue's author can close their own issue. One more path counts for a hand close: a
1271
+ PR of the project's repository, MERGED into the base, that GitHub lists as closing
1272
+ the issue (`closedByPullRequestsReferences` — `Closes #N` parsed but not acted on,
1273
+ the imboard#4116 shape); that still needs a merge, i.e. write access. A member issue that no longer resolves
1274
+ (deleted/transferred) reads `member-missing` — needs-operator, not an outage.
1275
+ - **Never over a failure trail.** Any evicted member, any member in a failure status
1276
+ (`ISSUE_UNIVERSAL_FAILURE_EDGES` + `evicted`/`requeued`), any member requeued out of
1277
+ the batch, a dissolved/stopped batch, or a `decision-pending` label on the anchor or a
1278
+ member (case-insensitive, `hasLabel`) leaves the anchor open. The ledger is re-checked
1279
+ on a fresh load immediately before the close.
1280
+ - **Idempotent and attributable.** The anchor gets one comment naming each member's
1281
+ shipping PR or commit, starting with `<!-- batch-close:v1 batch=<id> anchor -->`;
1282
+ only that marker on a comment by the authenticated gh user counts, so a rerun after a
1283
+ failed close closes without a second comment. `anchor_closed_at` is then recorded and
1284
+ the anchor is never polled again.
1285
+ - **Only the verified repository.** The pass runs only when `EngineDeps.anchorRepo` is
1286
+ set — `sched start` sets it from `resolveProjectRepo`, i.e. only when the cwd IS the
1287
+ project's repository — and every `gh` call names it (`-R`, explicit GraphQL owner/name).
1288
+ - **Members-closed reconcile evidence.** The same predicate is a third evidence kind
1289
+ for #686's stale-blocked reconcile (`members-closed`), which survives the mandated
1290
+ rebase-merge that makes the ancestry probe structurally dead. It finishes the terminal
1291
+ rail inline but never tears the blocked worktree down (it may hold unpushed work);
1292
+ the journal line says the worktree was kept.
1293
+
1294
+ Everything else is surfaced, never closed: `sched status --anchors` (opt-in; `status`
1295
+ makes no GitHub call without it) lists each still-open anchor of a batch no longer in
1296
+ flight as `closable`, `needs-operator` or `unknown`, with every member's GitHub and
1297
+ ledger state, stopping its reads at the first failed one. A blocked batch milestone now
1298
+ reads `next=operator`, not `next=done`.
1299
+
1300
+ Journal: `anchor-closed` (including "found already closed") and `anchor-close-failed`
1301
+ (deduped like `pr-watch-failed`: once per streak, re-announced every
1302
+ `JOURNAL_DEDUP_REANNOUNCE_TICKS`).
1303
+
1237
1304
  ## Hard-block labels are re-read every tick (#544)
1238
1305
 
1239
1306
  #507's enqueue pre-screen resolves an issue's GitHub labels in the CLI and lands the
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Batch anchor closure off the happy path (#768).
3
+ *
4
+ * A batch anchor used to close only when `batch-integrate` merged the batch PR
5
+ * and ran its post-merge step (#720). A batch that blocked, was recovered by
6
+ * hand, or whose members shipped through other PRs left its anchor open
7
+ * forever (imboard#4244, imboard#4253).
8
+ *
9
+ * The rule here is deliberately NARROW (operator decision on #768): an anchor
10
+ * closes only on POSITIVE evidence — every member issue CLOSED with
11
+ * `stateReason=COMPLETED` BY SHIPPED CODE (a PR merged into the batch's base,
12
+ * or a commit reachable from it), and no member evicted, handed back,
13
+ * requeued out of the batch, or carrying an unresolved failure. A close by a
14
+ * person, with no linked PR or commit, is not evidence: an issue's author can
15
+ * close their own issue without write access. Every other shape leaves the
16
+ * anchor alone and is surfaced by `sched status --anchors` as
17
+ * `needs-operator`: an anchor with a failure trail must stay open, because
18
+ * closing it would bury the trail. "Close on every terminal transition" was
19
+ * considered and rejected for exactly that reason.
20
+ *
21
+ * Pure verdicts plus one idempotent effect (`closeAnchor`); everything that
22
+ * talks to GitHub or git is injected, so tests never shell out.
23
+ */
24
+ import type { IssueCloseTruth } from './groundtruth';
25
+ import type { ExecFn } from './project';
26
+ import type { BatchEntry, BatchStatus, IssueStatus, SchedState } from './types';
27
+ /** Reads one issue's closure record; `undefined` = the poll failed (unreachable). */
28
+ export type IssueCloseReader = (issue: number) => IssueCloseTruth | undefined;
29
+ /**
30
+ * Whether commit `oid` is reachable from the batch's base branch on origin —
31
+ * `true` only when verified; `false` for "not reachable" and for "could not
32
+ * tell" alike, so an unverifiable commit never counts as shipped.
33
+ */
34
+ export type CommitInBase = (oid: string, baseBranch: string) => boolean;
35
+ /**
36
+ * Batch statuses whose anchors the ENGINE may close: the batch is no longer in
37
+ * flight and its members were not requeued or stopped wholesale.
38
+ */
39
+ export declare const ANCHOR_CLOSE_BATCH_STATUSES: ReadonlySet<BatchStatus>;
40
+ /**
41
+ * The batches in `statuses` whose anchor is set and not yet recorded closed —
42
+ * the one candidate filter the engine's close pass and the sweep share.
43
+ */
44
+ export declare function openAnchorBatches(state: SchedState, statuses: ReadonlySet<BatchStatus>): Array<BatchEntry & {
45
+ anchor: number;
46
+ }>;
47
+ /** `blocked (<reason>)` for a blocked batch with a reason, else the bare status. */
48
+ export declare function formatBatchStatus(batch: Pick<BatchEntry, 'status' | 'blocked_reason'>): string;
49
+ /**
50
+ * Why the LEDGER alone rules out closing `batch`'s anchor — empty when the
51
+ * ledger shows no failure trail. Costs no GitHub call, so callers check it
52
+ * before reading any issue state.
53
+ */
54
+ export declare function anchorLedgerBlockers(state: SchedState, batch: BatchEntry): string[];
55
+ /** One member's state as the sweep and the close comment show it. */
56
+ export interface AnchorMemberReport {
57
+ issue: number;
58
+ /** The member's ledger status, or null when the ledger no longer carries it. */
59
+ ledger_status: IssueStatus | null;
60
+ /** GitHub state, or `unknown` when the poll failed / was not made. */
61
+ github: 'OPEN' | 'CLOSED' | 'MISSING' | 'unknown';
62
+ state_reason: string | null;
63
+ /** The verified shipping evidence for a member that shipped; null otherwise. */
64
+ shipped_by: string | null;
65
+ }
66
+ /**
67
+ * The anchor-close predicate's answer. Every open-anchor verdict carries
68
+ * `reasons` (empty for `closable`; for `unknown`, which read failed — ground
69
+ * truth unreachable, so nothing is decided this pass).
70
+ */
71
+ export type AnchorVerdict = {
72
+ kind: 'anchor-closed';
73
+ } | {
74
+ kind: 'closable' | 'needs-operator' | 'unknown';
75
+ reasons: string[];
76
+ members: AnchorMemberReport[];
77
+ };
78
+ /** The verdict of an anchor that is still open. */
79
+ export type OpenAnchorVerdict = Exclude<AnchorVerdict, {
80
+ kind: 'anchor-closed';
81
+ }>;
82
+ /**
83
+ * The verified shipping evidence for a member closed as completed, or the
84
+ * reason it is not evidence. Only code that landed in `baseBranch` counts:
85
+ * a PR of `repo` (the pinned project repository) MERGED into it — as the
86
+ * close event's closer, or, for a hand close, as a closing reference — or a
87
+ * commit reachable from it. A hand close (no linked
88
+ * closer), an unmerged or other-base PR, and an unverifiable commit are all
89
+ * refusals — anyone who can close the issue could otherwise mint "shipped".
90
+ */
91
+ export declare function shippingEvidence(truth: IssueCloseTruth, baseBranch: string, repo: string | undefined, commitInBase: CommitInBase | undefined): {
92
+ shipped: string;
93
+ } | {
94
+ refused: string;
95
+ };
96
+ export interface MembersVerdictOptions {
97
+ /** Read every member even after a disqualifier (the sweep); the engine stops early. */
98
+ exhaustive?: boolean;
99
+ /** Reasons already known before any member is read (e.g. a handed-back anchor). */
100
+ extraReasons?: string[];
101
+ /** Verifies commit closers; without it a commit closer is never evidence. */
102
+ commitInBase?: CommitInBase;
103
+ /** The pinned `owner/name`; without it a PR closer is never evidence. */
104
+ repo?: string;
105
+ }
106
+ /**
107
+ * The member half of the predicate: every member CLOSED as `COMPLETED` by
108
+ * shipped code ({@link shippingEvidence}), none handed back, and the ledger
109
+ * clean. Shared by the anchor close and by `reconcileStaleBlockedBatches`'
110
+ * members-closed evidence, so the two can never disagree about what
111
+ * "shipped" means.
112
+ *
113
+ * `exhaustive: false` (the engine) stops at the first disqualifying fact and
114
+ * skips GitHub entirely when the ledger already disqualifies — a batch that
115
+ * stays blocked is re-checked every tick, so the cheap answer matters there.
116
+ * `exhaustive: true` (the `sched status --anchors` sweep) reads every member
117
+ * so the operator sees each one's state. Either way a failed read stops the
118
+ * reads: GitHub being unreachable is not worth N more timeouts.
119
+ */
120
+ export declare function membersShippedVerdict(state: SchedState, batch: BatchEntry, read: IssueCloseReader, opts?: MembersVerdictOptions): OpenAnchorVerdict;
121
+ /**
122
+ * The full anchor verdict: the anchor itself first (an already-closed anchor
123
+ * needs nothing; a handed-back anchor is the operator's), then
124
+ * {@link membersShippedVerdict}.
125
+ */
126
+ export declare function classifyAnchor(state: SchedState, batch: BatchEntry, read: IssueCloseReader, opts?: Omit<MembersVerdictOptions, 'extraReasons'>): AnchorVerdict;
127
+ /** The idempotency marker on the anchor-close comment — one per batch. */
128
+ export declare function anchorCloseMarker(batchId: string): string;
129
+ /** The comment posted on the anchor before it is closed. */
130
+ export declare function renderAnchorCloseComment(batch: BatchEntry, members: AnchorMemberReport[]): string;
131
+ /**
132
+ * `comments-unreadable`: the marker check could not run (or the gh identity
133
+ * could not be read), so nothing was posted; `comment-failed`/`close-failed`:
134
+ * that write failed.
135
+ */
136
+ export type AnchorCloseOutcome = 'closed' | 'comments-unreadable' | 'comment-failed' | 'close-failed';
137
+ /**
138
+ * Comment-then-close the anchor, idempotently (the #720 shape): the comment
139
+ * is posted only when the anchor does not already carry this batch's marker,
140
+ * so a rerun after a failed close closes without a second comment. The marker
141
+ * counts only at the START of a comment by the authenticated gh user — any
142
+ * commenter could otherwise post it and suppress the audit comment. When the
143
+ * existing comments cannot be read, nothing is posted: a duplicate comment is
144
+ * exactly what the marker exists to prevent.
145
+ *
146
+ * Every call names `repo` explicitly (`-R`): the anchor is never resolved from
147
+ * whatever repository the cwd happens to be.
148
+ */
149
+ export declare function closeAnchor(exec: ExecFn, repoDir: string, repo: string, batch: BatchEntry, anchorIssue: number, members: AnchorMemberReport[]): AnchorCloseOutcome;
150
+ /** A `sched status --anchors` sweep row (report-only — the sweep never closes anything). */
151
+ export interface AnchorReportItem {
152
+ batch: string;
153
+ anchor: number;
154
+ batch_status: BatchStatus;
155
+ verdict: OpenAnchorVerdict['kind'];
156
+ reasons: string[];
157
+ members: AnchorMemberReport[];
158
+ }
159
+ /**
160
+ * `sched status --anchors`' sweep (#768): every still-open anchor of a batch
161
+ * that is no longer in flight, marked `closable` (the strict condition holds —
162
+ * the engine will close it, within its 7-day window), `needs-operator`
163
+ * (anything else), or `unknown` (a GitHub read failed — nothing decided),
164
+ * with each member's state. Report-only by construction: it takes no exec for
165
+ * writes and has no way to close anything. The first failed read stops the
166
+ * sweep's reads; every later batch is reported `unknown` without a call.
167
+ */
168
+ export declare function sweepAnchors(state: SchedState, read: IssueCloseReader, opts?: {
169
+ repo?: string;
170
+ commitInBase?: CommitInBase;
171
+ }): AnchorReportItem[];
172
+ //# sourceMappingURL=anchor-close.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor-close.d.ts","sourceRoot":"","sources":["../src/anchor-close.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ /**
3
+ * Batch anchor closure off the happy path (#768).
4
+ *
5
+ * A batch anchor used to close only when `batch-integrate` merged the batch PR
6
+ * and ran its post-merge step (#720). A batch that blocked, was recovered by
7
+ * hand, or whose members shipped through other PRs left its anchor open
8
+ * forever (imboard#4244, imboard#4253).
9
+ *
10
+ * The rule here is deliberately NARROW (operator decision on #768): an anchor
11
+ * closes only on POSITIVE evidence — every member issue CLOSED with
12
+ * `stateReason=COMPLETED` BY SHIPPED CODE (a PR merged into the batch's base,
13
+ * or a commit reachable from it), and no member evicted, handed back,
14
+ * requeued out of the batch, or carrying an unresolved failure. A close by a
15
+ * person, with no linked PR or commit, is not evidence: an issue's author can
16
+ * close their own issue without write access. Every other shape leaves the
17
+ * anchor alone and is surfaced by `sched status --anchors` as
18
+ * `needs-operator`: an anchor with a failure trail must stay open, because
19
+ * closing it would bury the trail. "Close on every terminal transition" was
20
+ * considered and rejected for exactly that reason.
21
+ *
22
+ * Pure verdicts plus one idempotent effect (`closeAnchor`); everything that
23
+ * talks to GitHub or git is injected, so tests never shell out.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ANCHOR_CLOSE_BATCH_STATUSES = void 0;
27
+ exports.openAnchorBatches = openAnchorBatches;
28
+ exports.formatBatchStatus = formatBatchStatus;
29
+ exports.anchorLedgerBlockers = anchorLedgerBlockers;
30
+ exports.shippingEvidence = shippingEvidence;
31
+ exports.membersShippedVerdict = membersShippedVerdict;
32
+ exports.classifyAnchor = classifyAnchor;
33
+ exports.anchorCloseMarker = anchorCloseMarker;
34
+ exports.renderAnchorCloseComment = renderAnchorCloseComment;
35
+ exports.closeAnchor = closeAnchor;
36
+ exports.sweepAnchors = sweepAnchors;
37
+ const labels_1 = require("./labels");
38
+ const state_1 = require("./state");
39
+ /**
40
+ * Member ledger statuses that ARE a failure trail: the universal failure
41
+ * edges (derived, so a failure status added there is refused here too) plus
42
+ * the eviction rail. A member sitting in one of these was evicted, requeued,
43
+ * handed to a human, failed, or stopped — its anchor must stay open for an
44
+ * operator even if GitHub shows it closed.
45
+ */
46
+ const MEMBER_FAILURE_STATUSES = new Set([
47
+ ...state_1.ISSUE_UNIVERSAL_FAILURE_EDGES,
48
+ 'evicted',
49
+ 'requeued',
50
+ ]);
51
+ /**
52
+ * Batch statuses whose anchors the ENGINE may close: the batch is no longer in
53
+ * flight and its members were not requeued or stopped wholesale.
54
+ */
55
+ exports.ANCHOR_CLOSE_BATCH_STATUSES = new Set(['blocked', 'done']);
56
+ /** Batch statuses whose members were requeued or stopped wholesale — never auto-closable. */
57
+ const FAILED_BATCH_STATUSES = new Set([
58
+ 'dissolving',
59
+ 'dissolved',
60
+ 'stopped',
61
+ ]);
62
+ /**
63
+ * Batch statuses whose anchors the sweep inspects: every batch no longer in
64
+ * flight — the engine's set plus the requeued/stopped ones it never closes.
65
+ */
66
+ const SWEEP_BATCH_STATUSES = new Set([
67
+ ...exports.ANCHOR_CLOSE_BATCH_STATUSES,
68
+ ...FAILED_BATCH_STATUSES,
69
+ ]);
70
+ /**
71
+ * The batches in `statuses` whose anchor is set and not yet recorded closed —
72
+ * the one candidate filter the engine's close pass and the sweep share.
73
+ */
74
+ function openAnchorBatches(state, statuses) {
75
+ return state.batches.filter((b) => b.anchor !== null && b.anchor_closed_at === null && statuses.has(b.status));
76
+ }
77
+ /** `blocked (<reason>)` for a blocked batch with a reason, else the bare status. */
78
+ function formatBatchStatus(batch) {
79
+ return batch.status === 'blocked' && batch.blocked_reason
80
+ ? `blocked (${batch.blocked_reason})`
81
+ : batch.status;
82
+ }
83
+ /**
84
+ * Why the LEDGER alone rules out closing `batch`'s anchor — empty when the
85
+ * ledger shows no failure trail. Costs no GitHub call, so callers check it
86
+ * before reading any issue state.
87
+ */
88
+ function anchorLedgerBlockers(state, batch) {
89
+ const reasons = [];
90
+ if (FAILED_BATCH_STATUSES.has(batch.status))
91
+ reasons.push(`batch-${batch.status}`);
92
+ if (batch.members.length === 0)
93
+ reasons.push('no-members');
94
+ for (const e of (0, state_1.distinctEvictions)(batch.evictions)) {
95
+ reasons.push(`member-evicted:#${e.issue}`);
96
+ }
97
+ for (const issue of batch.members) {
98
+ const entry = (0, state_1.findEntry)(state, issue);
99
+ if (!entry) {
100
+ reasons.push(`member-not-in-ledger:#${issue}`);
101
+ continue;
102
+ }
103
+ if (MEMBER_FAILURE_STATUSES.has(entry.status)) {
104
+ reasons.push(`member-${entry.status}:#${issue}`);
105
+ }
106
+ else if (entry.mode !== 'slot' || entry.batch !== batch.id) {
107
+ // Requeued out of this batch (dissolve / abandon → full-cycle): its
108
+ // outcome is no longer this batch's to vouch for.
109
+ reasons.push(`member-requeued:#${issue}`);
110
+ }
111
+ }
112
+ return [...new Set(reasons)];
113
+ }
114
+ /**
115
+ * The verified shipping evidence for a member closed as completed, or the
116
+ * reason it is not evidence. Only code that landed in `baseBranch` counts:
117
+ * a PR of `repo` (the pinned project repository) MERGED into it — as the
118
+ * close event's closer, or, for a hand close, as a closing reference — or a
119
+ * commit reachable from it. A hand close (no linked
120
+ * closer), an unmerged or other-base PR, and an unverifiable commit are all
121
+ * refusals — anyone who can close the issue could otherwise mint "shipped".
122
+ */
123
+ function shippingEvidence(truth, baseBranch, repo, commitInBase) {
124
+ const closer = truth.closer;
125
+ if (closer === null) {
126
+ // Closed by hand. Still shipped when a PR of the pinned repo, MERGED into
127
+ // the base, names it as closed (`Closes #N` — GitHub parsed it, then did
128
+ // not act: the imboard#4116 shape). A merge needs write access, so an
129
+ // author's self-close alone still never counts.
130
+ const ref = truth.closingPrs.find((pr) => pr.merged &&
131
+ pr.baseRefName === baseBranch &&
132
+ repo !== undefined &&
133
+ pr.repo?.toLowerCase() === repo.toLowerCase());
134
+ return ref !== undefined
135
+ ? { shipped: `PR #${ref.number} (closing reference; issue closed by hand)` }
136
+ : { refused: 'closed-by-hand' };
137
+ }
138
+ if (closer.kind === 'pr') {
139
+ // A PR in ANOTHER repository can close this issue (`Fixes owner/repo#N`)
140
+ // and merge into a same-named base — that is not code in this project.
141
+ if (repo === undefined || closer.repo?.toLowerCase() !== repo.toLowerCase()) {
142
+ return { refused: `closer-pr-${closer.number}-other-repo` };
143
+ }
144
+ if (!closer.merged)
145
+ return { refused: `closer-pr-unmerged-${closer.number}` };
146
+ if (closer.baseRefName !== baseBranch) {
147
+ return { refused: `closer-pr-${closer.number}-not-into-${baseBranch}` };
148
+ }
149
+ return { shipped: `PR #${closer.number}` };
150
+ }
151
+ if (commitInBase?.(closer.oid, baseBranch) !== true) {
152
+ return { refused: `closer-commit-${closer.oid.slice(0, 12)}-not-in-${baseBranch}` };
153
+ }
154
+ return { shipped: `commit ${closer.oid.slice(0, 12)}` };
155
+ }
156
+ /**
157
+ * The member half of the predicate: every member CLOSED as `COMPLETED` by
158
+ * shipped code ({@link shippingEvidence}), none handed back, and the ledger
159
+ * clean. Shared by the anchor close and by `reconcileStaleBlockedBatches`'
160
+ * members-closed evidence, so the two can never disagree about what
161
+ * "shipped" means.
162
+ *
163
+ * `exhaustive: false` (the engine) stops at the first disqualifying fact and
164
+ * skips GitHub entirely when the ledger already disqualifies — a batch that
165
+ * stays blocked is re-checked every tick, so the cheap answer matters there.
166
+ * `exhaustive: true` (the `sched status --anchors` sweep) reads every member
167
+ * so the operator sees each one's state. Either way a failed read stops the
168
+ * reads: GitHub being unreachable is not worth N more timeouts.
169
+ */
170
+ function membersShippedVerdict(state, batch, read, opts = {}) {
171
+ const exhaustive = opts.exhaustive === true;
172
+ const reasons = [...(opts.extraReasons ?? []), ...anchorLedgerBlockers(state, batch)];
173
+ const members = batch.members.map((issue) => ({
174
+ issue,
175
+ ledger_status: (0, state_1.findEntry)(state, issue)?.status ?? null,
176
+ github: 'unknown',
177
+ state_reason: null,
178
+ shipped_by: null,
179
+ }));
180
+ if (reasons.length > 0 && !exhaustive)
181
+ return { kind: 'needs-operator', reasons, members };
182
+ let unreachable = null;
183
+ for (const member of members) {
184
+ const truth = read(member.issue);
185
+ if (truth === undefined) {
186
+ unreachable = member.issue;
187
+ break;
188
+ }
189
+ if (truth.state === 'MISSING') {
190
+ member.github = 'MISSING';
191
+ reasons.push(`member-missing:#${member.issue}`);
192
+ if (!exhaustive)
193
+ break;
194
+ continue;
195
+ }
196
+ member.github = truth.state;
197
+ member.state_reason = truth.stateReason;
198
+ if (truth.state === 'OPEN') {
199
+ reasons.push(`member-open:#${member.issue}`);
200
+ }
201
+ else if (truth.stateReason !== 'COMPLETED') {
202
+ reasons.push(`member-closed-${(truth.stateReason ?? 'unknown').toLowerCase()}:#${member.issue}`);
203
+ }
204
+ else {
205
+ const evidence = shippingEvidence(truth, batch.base_branch, opts.repo, opts.commitInBase);
206
+ if ('shipped' in evidence)
207
+ member.shipped_by = evidence.shipped;
208
+ else
209
+ reasons.push(`member-${evidence.refused}:#${member.issue}`);
210
+ }
211
+ if ((0, labels_1.hasLabel)(truth.labels, labels_1.DECISION_PENDING_LABEL)) {
212
+ reasons.push(`member-handed-back:#${member.issue}`);
213
+ }
214
+ if (reasons.length > 0 && !exhaustive)
215
+ break;
216
+ }
217
+ // A known disqualifier outranks an unreachable read: whatever the missing
218
+ // poll would have said, the batch is not closable on this pass.
219
+ if (reasons.length > 0)
220
+ return { kind: 'needs-operator', reasons, members };
221
+ if (unreachable !== null) {
222
+ return { kind: 'unknown', reasons: [`issue #${unreachable} unreachable`], members };
223
+ }
224
+ return { kind: 'closable', reasons: [], members };
225
+ }
226
+ /**
227
+ * The full anchor verdict: the anchor itself first (an already-closed anchor
228
+ * needs nothing; a handed-back anchor is the operator's), then
229
+ * {@link membersShippedVerdict}.
230
+ */
231
+ function classifyAnchor(state, batch, read, opts = {}) {
232
+ if (batch.anchor === null) {
233
+ return { kind: 'needs-operator', reasons: ['no-anchor'], members: [] };
234
+ }
235
+ const anchor = read(batch.anchor);
236
+ if (anchor === undefined) {
237
+ return { kind: 'unknown', reasons: [`anchor #${batch.anchor} unreachable`], members: [] };
238
+ }
239
+ if (anchor.state === 'CLOSED')
240
+ return { kind: 'anchor-closed' };
241
+ if (anchor.state === 'MISSING') {
242
+ return { kind: 'needs-operator', reasons: ['anchor-missing'], members: [] };
243
+ }
244
+ const extraReasons = (0, labels_1.hasLabel)(anchor.labels, labels_1.DECISION_PENDING_LABEL)
245
+ ? ['anchor-handed-back']
246
+ : [];
247
+ return membersShippedVerdict(state, batch, read, { ...opts, extraReasons });
248
+ }
249
+ /** The idempotency marker on the anchor-close comment — one per batch. */
250
+ function anchorCloseMarker(batchId) {
251
+ return `<!-- batch-close:v1 batch=${batchId} anchor -->`;
252
+ }
253
+ /** The comment posted on the anchor before it is closed. */
254
+ function renderAnchorCloseComment(batch, members) {
255
+ return [
256
+ anchorCloseMarker(batch.id),
257
+ `**Batch \`${batch.id}\` anchor closed by sched** — every member issue was closed as completed by code that landed in \`${batch.base_branch}\`, and none was evicted, handed back, requeued, or left with a failure.`,
258
+ '',
259
+ '| member | shipped by |',
260
+ '|---|---|',
261
+ ...members.map((m) => `| #${m.issue} | ${m.shipped_by ?? 'unknown'} |`),
262
+ '',
263
+ `Batch ledger status at close: \`${formatBatchStatus(batch)}\`.`,
264
+ ].join('\n');
265
+ }
266
+ /** A GitHub login — the only shape interpolated into the comment filter below. */
267
+ const GH_LOGIN_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})(?:\[bot\])?$/;
268
+ /**
269
+ * Comment-then-close the anchor, idempotently (the #720 shape): the comment
270
+ * is posted only when the anchor does not already carry this batch's marker,
271
+ * so a rerun after a failed close closes without a second comment. The marker
272
+ * counts only at the START of a comment by the authenticated gh user — any
273
+ * commenter could otherwise post it and suppress the audit comment. When the
274
+ * existing comments cannot be read, nothing is posted: a duplicate comment is
275
+ * exactly what the marker exists to prevent.
276
+ *
277
+ * Every call names `repo` explicitly (`-R`): the anchor is never resolved from
278
+ * whatever repository the cwd happens to be.
279
+ */
280
+ function closeAnchor(exec, repoDir, repo, batch, anchorIssue, members) {
281
+ const anchor = String(anchorIssue);
282
+ const login = exec('gh', ['api', 'user', '--jq', '.login'], repoDir)?.trim();
283
+ if (login === undefined || !GH_LOGIN_RE.test(login))
284
+ return 'comments-unreadable';
285
+ const mine = exec('gh', [
286
+ 'issue',
287
+ 'view',
288
+ anchor,
289
+ '-R',
290
+ repo,
291
+ '--json',
292
+ 'comments',
293
+ '--jq',
294
+ `[.comments[] | select(.author.login == "${login}") | .body]`,
295
+ ], repoDir);
296
+ let bodies;
297
+ try {
298
+ bodies = mine === null ? null : JSON.parse(mine);
299
+ }
300
+ catch {
301
+ bodies = null;
302
+ }
303
+ if (!Array.isArray(bodies))
304
+ return 'comments-unreadable';
305
+ const marker = anchorCloseMarker(batch.id);
306
+ if (!bodies.some((b) => typeof b === 'string' && b.startsWith(marker))) {
307
+ const body = renderAnchorCloseComment(batch, members);
308
+ if (exec('gh', ['issue', 'comment', anchor, '-R', repo, '--body', body], repoDir) === null) {
309
+ return 'comment-failed';
310
+ }
311
+ }
312
+ return exec('gh', ['issue', 'close', anchor, '-R', repo, '--reason', 'completed'], repoDir) ===
313
+ null
314
+ ? 'close-failed'
315
+ : 'closed';
316
+ }
317
+ /**
318
+ * `sched status --anchors`' sweep (#768): every still-open anchor of a batch
319
+ * that is no longer in flight, marked `closable` (the strict condition holds —
320
+ * the engine will close it, within its 7-day window), `needs-operator`
321
+ * (anything else), or `unknown` (a GitHub read failed — nothing decided),
322
+ * with each member's state. Report-only by construction: it takes no exec for
323
+ * writes and has no way to close anything. The first failed read stops the
324
+ * sweep's reads; every later batch is reported `unknown` without a call.
325
+ */
326
+ function sweepAnchors(state, read, opts = {}) {
327
+ const items = [];
328
+ let aborted = false;
329
+ for (const batch of openAnchorBatches(state, SWEEP_BATCH_STATUSES)) {
330
+ const verdict = aborted
331
+ ? { kind: 'unknown', reasons: ['GitHub unreachable — sweep aborted'], members: [] }
332
+ : classifyAnchor(state, batch, read, { exhaustive: true, ...opts });
333
+ if (verdict.kind === 'anchor-closed')
334
+ continue;
335
+ if (verdict.kind === 'unknown')
336
+ aborted = true;
337
+ items.push({
338
+ batch: batch.id,
339
+ anchor: batch.anchor,
340
+ batch_status: batch.status,
341
+ verdict: verdict.kind,
342
+ reasons: verdict.reasons,
343
+ members: verdict.members,
344
+ });
345
+ }
346
+ return items;
347
+ }
348
+ //# sourceMappingURL=anchor-close.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor-close.js","sourceRoot":"","sources":["../src/anchor-close.ts"],"names":[],"mappings":"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"}