@ai-dossier/sched 0.45.0 → 0.46.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/README.md +67 -5
- package/dist/anchor-close.d.ts +172 -0
- package/dist/anchor-close.d.ts.map +1 -0
- package/dist/anchor-close.js +348 -0
- package/dist/anchor-close.js.map +1 -0
- package/dist/batch-dispatch.d.ts +8 -0
- package/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +215 -14
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/dispatch.d.ts +11 -1
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +25 -4
- package/dist/dispatch.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/groundtruth.d.ts +102 -0
- package/dist/groundtruth.d.ts.map +1 -1
- package/dist/groundtruth.js +162 -3
- package/dist/groundtruth.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -5
- package/dist/index.js.map +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +14 -2
- package/dist/labels.js.map +1 -1
- package/dist/project.d.ts +10 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +27 -0
- package/dist/project.js.map +1 -1
- package/dist/recovery.js +1 -1
- package/dist/recovery.js.map +1 -1
- package/dist/state.d.ts +12 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +39 -4
- package/dist/state.js.map +1 -1
- package/dist/status.d.ts +16 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +10 -1
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +25 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -1
- package/dist/types.js.map +1 -1
- 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)
|
|
@@ -688,9 +688,15 @@ member got the light, relevance-scoped review). `review=full` is how a risk-floo
|
|
|
688
688
|
The floor follows the member: its bounded fix attempt dispatches at `strong` (not
|
|
689
689
|
`FIX_ATTEMPT_TIER`), and an eviction/abandon requeue to full-cycle rewrites its `tier` to the
|
|
690
690
|
floored value (and `review` back to `light`, a slot-only concept) so the full cycle keeps it.
|
|
691
|
-
- **Prompt** — `buildMemberPrompt` substitutes a `{review}` placeholder
|
|
692
|
-
|
|
693
|
-
|
|
691
|
+
- **Prompt** — `buildMemberPrompt` substitutes a `{review}` placeholder.
|
|
692
|
+
`DEFAULT_MEMBER_PROMPT_TEMPLATE` places `{review}` itself (#804), so both `light` and `full`
|
|
693
|
+
members see their level and are told to run member-cycle Step 4b's self-review at it before
|
|
694
|
+
handover (`full` → the full review-issue tier incl. Security; `light` → at least
|
|
695
|
+
Conformance), then post `review done` naming the agents that ran — never `agents_done=0`.
|
|
696
|
+
An operator `member_prompt` template without `{review}` gets `FULL_REVIEW_MEMBER_DIRECTIVE`
|
|
697
|
+
appended for a `review=full` member; a `light` member's prompt from such a template is
|
|
698
|
+
byte-identical to before. A member's `review partial` (an agent could not finish) is a
|
|
699
|
+
terminal hand-back like `blocked` (`reason=review-partial:<agents_pending>`).
|
|
694
700
|
- **Status** — `sched status`'s Queue table has a `review` column for slot members, and the
|
|
695
701
|
`tier` column shows the floor as `mid→strong` when it applies.
|
|
696
702
|
|
|
@@ -1034,6 +1040,14 @@ import {
|
|
|
1034
1040
|
// so batch-dispatch.ts can share it.
|
|
1035
1041
|
TRANSITIONS, // the transition tables themselves (for previews)
|
|
1036
1042
|
buildStatusReport, // machine-readable status incl. blocked/failed sets
|
|
1043
|
+
// (+ optional `anchorSweep` → `anchors`, #768; null without it)
|
|
1044
|
+
sweepAnchors, classifyAnchor, membersShippedVerdict, anchorLedgerBlockers,
|
|
1045
|
+
shippingEvidence, // #768 anchor-close predicate (pure; readers injected)
|
|
1046
|
+
closeAnchor, // #768 idempotent comment-then-close, `-R <repo>` explicit
|
|
1047
|
+
anchorCloseMarker, renderAnchorCloseComment, openAnchorBatches, formatBatchStatus,
|
|
1048
|
+
issueCloseReader, parseIssueCloseTruthJson, parseRepoName,
|
|
1049
|
+
resolveProjectRepo, // #768 owner/name of the cwd repo only when it IS the project's
|
|
1050
|
+
hasLabel, // case-insensitive label match (#768)
|
|
1037
1051
|
validateState, // strict persisted-state validation (1.0.0-1.13.0 files migrate)
|
|
1038
1052
|
DEFAULT_ISSUE_PRIORITY, DEFAULT_BATCH_PRIORITY, // priority defaults (0 / 10, #565)
|
|
1039
1053
|
IllegalTransitionError, EnqueueError, CorruptStateError, LockTimeoutError,
|
|
@@ -1213,7 +1227,8 @@ after-the-fact recovery, not a missing-data bug.
|
|
|
1213
1227
|
`member_gates`/`blocked_reason` — #565/#583, slot
|
|
1214
1228
|
`stale_milestone_ignored_for` backfill to `null` — #610, and state-level
|
|
1215
1229
|
`consecutive_dispatch_api_errors`/`dispatch_pause_reset_at` backfill to `0`/`null` —
|
|
1216
|
-
#629
|
|
1230
|
+
#629; batch `anchor_closed_at` and `anchor_close_failed_reason`/`_since`/`_ticks`
|
|
1231
|
+
backfill to `null`/`null`/`null`/`0` — #768, schema 1.22.0).
|
|
1217
1232
|
- **`max_slots`** bounds live units (`assigned | running | recovering`); dependency
|
|
1218
1233
|
edges gate readiness — an issue with an unmerged dependency, and a batch behind an
|
|
1219
1234
|
unmerged batch, are never runnable.
|
|
@@ -1245,6 +1260,53 @@ nobody came back to, each with its exact remedy:
|
|
|
1245
1260
|
the recovery rail never respawns it (even after `sched resume`). Report slots are exempt:
|
|
1246
1261
|
their issue is closed at merge by design. Remedy: `sched stop --issue N`.
|
|
1247
1262
|
|
|
1263
|
+
## Batch anchors close only on positive evidence (#768)
|
|
1264
|
+
|
|
1265
|
+
A batch's anchor issue used to close only on the happy path (batch-integrate's
|
|
1266
|
+
post-merge step, #720). A batch that blocked, was recovered by hand, or whose members
|
|
1267
|
+
shipped through PRs of their own left its anchor open forever (imboard#4244,
|
|
1268
|
+
imboard#4253). Each tick now runs `reconcileAnchorClosure` (`batch-dispatch.ts`, with the
|
|
1269
|
+
predicate in `anchor-close.ts`) over `blocked`/`done` batches touched within the 7-day
|
|
1270
|
+
`STALE_BLOCKED_RECONCILE_WINDOW_MS` whose anchor is not yet recorded closed:
|
|
1271
|
+
|
|
1272
|
+
- **Closes only when every member shipped.** Each member issue must be CLOSED with
|
|
1273
|
+
`stateReason=COMPLETED`, and its close event's closer must be a PR of the project's own repository MERGED
|
|
1274
|
+
into the batch's `base_branch` or a commit reachable from `origin/<base>`
|
|
1275
|
+
(`shippingEvidence`). A hand close — no linked PR or commit — is not evidence: an
|
|
1276
|
+
issue's author can close their own issue. One more path counts for a hand close: a
|
|
1277
|
+
PR of the project's repository, MERGED into the base, that GitHub lists as closing
|
|
1278
|
+
the issue (`closedByPullRequestsReferences` — `Closes #N` parsed but not acted on,
|
|
1279
|
+
the imboard#4116 shape); that still needs a merge, i.e. write access. A member issue that no longer resolves
|
|
1280
|
+
(deleted/transferred) reads `member-missing` — needs-operator, not an outage.
|
|
1281
|
+
- **Never over a failure trail.** Any evicted member, any member in a failure status
|
|
1282
|
+
(`ISSUE_UNIVERSAL_FAILURE_EDGES` + `evicted`/`requeued`), any member requeued out of
|
|
1283
|
+
the batch, a dissolved/stopped batch, or a `decision-pending` label on the anchor or a
|
|
1284
|
+
member (case-insensitive, `hasLabel`) leaves the anchor open. The ledger is re-checked
|
|
1285
|
+
on a fresh load immediately before the close.
|
|
1286
|
+
- **Idempotent and attributable.** The anchor gets one comment naming each member's
|
|
1287
|
+
shipping PR or commit, starting with `<!-- batch-close:v1 batch=<id> anchor -->`;
|
|
1288
|
+
only that marker on a comment by the authenticated gh user counts, so a rerun after a
|
|
1289
|
+
failed close closes without a second comment. `anchor_closed_at` is then recorded and
|
|
1290
|
+
the anchor is never polled again.
|
|
1291
|
+
- **Only the verified repository.** The pass runs only when `EngineDeps.anchorRepo` is
|
|
1292
|
+
set — `sched start` sets it from `resolveProjectRepo`, i.e. only when the cwd IS the
|
|
1293
|
+
project's repository — and every `gh` call names it (`-R`, explicit GraphQL owner/name).
|
|
1294
|
+
- **Members-closed reconcile evidence.** The same predicate is a third evidence kind
|
|
1295
|
+
for #686's stale-blocked reconcile (`members-closed`), which survives the mandated
|
|
1296
|
+
rebase-merge that makes the ancestry probe structurally dead. It finishes the terminal
|
|
1297
|
+
rail inline but never tears the blocked worktree down (it may hold unpushed work);
|
|
1298
|
+
the journal line says the worktree was kept.
|
|
1299
|
+
|
|
1300
|
+
Everything else is surfaced, never closed: `sched status --anchors` (opt-in; `status`
|
|
1301
|
+
makes no GitHub call without it) lists each still-open anchor of a batch no longer in
|
|
1302
|
+
flight as `closable`, `needs-operator` or `unknown`, with every member's GitHub and
|
|
1303
|
+
ledger state, stopping its reads at the first failed one. A blocked batch milestone now
|
|
1304
|
+
reads `next=operator`, not `next=done`.
|
|
1305
|
+
|
|
1306
|
+
Journal: `anchor-closed` (including "found already closed") and `anchor-close-failed`
|
|
1307
|
+
(deduped like `pr-watch-failed`: once per streak, re-announced every
|
|
1308
|
+
`JOURNAL_DEDUP_REANNOUNCE_TICKS`).
|
|
1309
|
+
|
|
1248
1310
|
## Hard-block labels are re-read every tick (#544)
|
|
1249
1311
|
|
|
1250
1312
|
#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":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEhF,qFAAqF;AACrF,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;AAE9E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;AAexE;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,WAAW,CAAgC,CAAC;AAkBlG;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,WAAW,CAAC,WAAW,CAAC,GACjC,KAAK,CAAC,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAKxC;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAI9F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAsBnF;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC,sEAAsE;IACtE,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAClD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IACE,IAAI,EAAE,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B,CAAC;AAEN,mDAAmD;AACnD,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAC;AAElF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAkC3C;AAED,MAAM,WAAW,qBAAqB;IACpC,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,gBAAgB,EACtB,IAAI,GAAE,qBAA0B,GAC/B,iBAAiB,CAkDnB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,gBAAgB,EACtB,IAAI,GAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,CAAM,GACrD,aAAa,CAgBf;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,4DAA4D;AAC5D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAWjG;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,qBAAqB,GACrB,gBAAgB,GAChB,cAAc,CAAC;AAKnB;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,kBAAkB,EAAE,GAC5B,kBAAkB,CAqCpB;AAED,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,WAAW,CAAC;IAC1B,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,gBAAgB,EACtB,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CAAO,GACxD,gBAAgB,EAAE,CAmBpB"}
|
|
@@ -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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"}
|