@arjunkhera/atlas 0.0.0 → 0.2.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.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +18 -0
  2. package/.mcp.json +18 -0
  3. package/README.md +32 -1
  4. package/agents/artifact-format/example.html +71 -0
  5. package/agents/artifact-format/system.css +273 -0
  6. package/agents/artifact-renderer.md +132 -0
  7. package/agents/capability-reader.md +100 -0
  8. package/agents/code-explorer.md +108 -0
  9. package/agents/reviewer-architect.md +78 -0
  10. package/agents/reviewer-pm.md +81 -0
  11. package/agents/reviewer-security.md +82 -0
  12. package/bin/atlas +5 -0
  13. package/door/cli.mjs +280 -0
  14. package/door/lib/helpers.mjs +139 -0
  15. package/door/lib/install.mjs +260 -0
  16. package/door/lib/kind-drift.mjs +76 -0
  17. package/door/lib/onboarding.mjs +130 -0
  18. package/door/lib/pull-request.mjs +119 -0
  19. package/door/lib/releases.mjs +42 -0
  20. package/door/lib/status.mjs +47 -0
  21. package/door/lib/tooling.mjs +119 -0
  22. package/package.json +24 -3
  23. package/shape/check.mjs +1797 -0
  24. package/shape/releases.json +15 -0
  25. package/skills/sdlc-task/SKILL.md +174 -0
  26. package/skills/sdlc-task/lifecycle.yaml +27 -0
  27. package/skills/sdlc-task/templates/design-doc.md +118 -0
  28. package/skills/sdlc-task/templates/resume-anchor.md +25 -0
  29. package/work/code-read.mjs +65 -0
  30. package/work/lib/action-store.mjs +84 -0
  31. package/work/lib/capability.mjs +487 -0
  32. package/work/lib/child-env.mjs +11 -0
  33. package/work/lib/client.mjs +214 -0
  34. package/work/lib/code-read.mjs +238 -0
  35. package/work/lib/delivery.mjs +624 -0
  36. package/work/lib/mcp-stdio.mjs +68 -0
  37. package/work/lib/questions.mjs +278 -0
  38. package/work/lib/sweep.mjs +189 -0
  39. package/work/lib/verb-fields.mjs +53 -0
  40. package/work/lib/verbs.mjs +511 -0
  41. package/work/manifest-0.5.0.json +609 -0
  42. package/work/mcp.mjs +216 -0
  43. package/work/protected-paths.json +38 -0
@@ -0,0 +1,624 @@
1
+ // Atlas M1 slice L3: delivery, reopen and movement.
2
+ // Design: the Atlas M1 study, D1 (the delivery and
3
+ // reopen marks), D2 (the verbs and the door), D3 (the "In flight" view),
4
+ // D6 findings R1, R2, R9, R10, R14, and the frozen L3 paragraph of the lock
5
+ // block. The verbs run behind the same door, the same journal, the same
6
+ // registry checks and the same writer stamp as slices L1 and L2.
7
+ //
8
+ // Three rules hold this slice together:
9
+ // 1. The done verb runs the three live checks itself, with GET only. The
10
+ // caller never supplies a merge state, a gate run or a verdict (R10).
11
+ // 2. A reopen never rewrites a delivery. Each mark is its own record; a
12
+ // reopen points at the delivery it corrects with the corrects edge (R9).
13
+ // 3. Movement is read, never stored. The view computes it at question time
14
+ // from the marks and from the repos' pull requests (D3 rule 5, R1).
15
+ import { refuse, text, isoDate, url, stringList, sha256 } from './verb-fields.mjs';
16
+ import { oneLine, ROW_LINE_MAX } from './questions.mjs';
17
+
18
+ export const DELIVERY_WRITE_VERBS = Object.freeze(['item_done', 'item_reopen']);
19
+ export const DELIVERY_READ_VERBS = Object.freeze(['in_flight']);
20
+
21
+ // A check run that concluded one of these did not pass the gate. GitHub's own
22
+ // vocabulary; neutral, skipped and stale are not failures.
23
+ export const FATAL_CONCLUSIONS = Object.freeze(['failure', 'timed_out', 'action_required', 'cancelled', 'startup_failure']);
24
+ const PASSING_CONCLUSION = 'success';
25
+
26
+ const MAX_PRS = 32;
27
+ const MAX_REPOS = 16;
28
+ const MAX_ITEMS = 500;
29
+ const PRS_PER_REPO = 100;
30
+ const CHECK_RUNS = 100;
31
+ const UNREGISTERED_SHOWN = 5;
32
+ const UNREGISTERED_KEPT = 25;
33
+
34
+ // One list line: the tail is what the reader needs, so the title yields to it
35
+ // inside the budget, and the indent goes on afterwards (reviews H10 and H20).
36
+ export function listLine(head, tail, indent = ' ', max = ROW_LINE_MAX) {
37
+ const end = oneLine(tail, max - indent.length);
38
+ const budget = max - end.length - indent.length - 3;
39
+ return budget < 1 ? `${indent}${end}` : `${indent}${oneLine(head, budget)} · ${end}`;
40
+ }
41
+ const DAY = 86_400_000;
42
+
43
+ const PR_URL = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/pull\/(\d+)(?:[/?#].*)?$/;
44
+ // A deploy identity as a health endpoint reports it: "sha-3cceb68", "3cceb68",
45
+ // "git-3cceb68". Anything else is not a commit and is refused.
46
+ const COMMIT = /^(?:sha-|commit-|git-|rev-)?([0-9a-f]{7,40})$/;
47
+ const COMMIT_FIELDS = ['commit', 'sha', 'revision', 'version', 'build', 'git_sha'];
48
+
49
+ export const parsePullUrl = (value, name) => {
50
+ const match = PR_URL.exec(text(value, name, { max: 500 }).trim());
51
+ if (!match) refuse(`${name} must be a GitHub pull request URL, for example https://github.com/owner/repo/pull/12`);
52
+ return { repo: match[1], number: Number(match[2]), url: `https://github.com/${match[1]}/pull/${match[2]}` };
53
+ };
54
+
55
+ // The deployed commit, read from the health endpoint's own answer. The caller
56
+ // never supplies it; a health endpoint that names no commit fails the check.
57
+ export function deployedCommitOf(body) {
58
+ let parsed;
59
+ try { parsed = JSON.parse(body); } catch { return null; }
60
+ if (!parsed || typeof parsed !== 'object') return null;
61
+ for (const key of COMMIT_FIELDS) {
62
+ const value = parsed[key];
63
+ if (typeof value !== 'string') continue;
64
+ const match = COMMIT.exec(value.trim().toLowerCase());
65
+ if (match) return { commit: match[1], field: key, reported: value };
66
+ }
67
+ return null;
68
+ }
69
+
70
+ // GitHub lists every check run a commit ever had, re-runs included. Only the
71
+ // last run of each name says what the gate concluded.
72
+ export function latestCheckRuns(runs) {
73
+ const byName = new Map();
74
+ for (const run of runs ?? []) {
75
+ const name = typeof run?.name === 'string' ? run.name : '';
76
+ if (!name) continue;
77
+ if (!byName.has(name)) byName.set(name, []);
78
+ byName.get(name).push(run);
79
+ }
80
+ const order = (run) => `${String(run.completed_at ?? run.started_at ?? '')}#${String(run.id ?? 0).padStart(24, '0')}`;
81
+ return [...byName.entries()]
82
+ .map(([name, list]) => {
83
+ const sorted = [...list].sort((a, b) => order(a).localeCompare(order(b)));
84
+ const last = sorted.at(-1);
85
+ // Every earlier run of this name that concluded fatally. A red run that a
86
+ // later run superseded is recorded, never dropped: only a later SUCCESS
87
+ // clears it, and any other later conclusion leaves it standing.
88
+ const superseded = sorted.slice(0, -1).filter((run) => FATAL_CONCLUSIONS.includes(run.conclusion ?? ''))
89
+ .map((run) => ({ conclusion: run.conclusion, url: run.html_url ?? '', completed_at: run.completed_at ?? null }));
90
+ return { name, conclusion: last.conclusion ?? null, status: last.status ?? null, url: last.html_url ?? '', completed_at: last.completed_at ?? null, attempts: sorted.length, ...(superseded.length ? { superseded_failures: superseded } : {}) };
91
+ })
92
+ .sort((a, b) => a.name.localeCompare(b.name));
93
+ }
94
+
95
+ export const daysBetween = (from, to) => {
96
+ const start = Date.parse(from ?? ''), end = Date.parse(to ?? '');
97
+ if (Number.isNaN(start) || Number.isNaN(end)) return null;
98
+ return Math.max(0, Math.floor((end - start) / DAY));
99
+ };
100
+
101
+ // The legacy seal of the first slice, read as a delivery mark (R14). The seal
102
+ // is never rewritten; the view reads it where it lies.
103
+ export function legacySealOf(item) {
104
+ const sealed = item.fields?.sealed_at;
105
+ if (typeof sealed !== 'string' || !sealed) return null;
106
+ return {
107
+ mark_id: null, kind: 'delivery', verdict: 'legacy', marked_at: sealed,
108
+ item: item.id, product: item.fields?.product ?? '', prs: [],
109
+ source: `legacy seal, verdict ${item.fields?.seal_verdict ?? 'unknown'}`,
110
+ };
111
+ }
112
+
113
+ // The marks of each item, oldest first. One read of the mark records serves
114
+ // every item in a view.
115
+ export function marksByItem(rows, itemIds) {
116
+ const byItem = new Map();
117
+ for (const row of rows ?? []) {
118
+ const id = row.fields?.item;
119
+ if (typeof id !== 'string' || (itemIds && !itemIds.has(id))) continue;
120
+ if (!byItem.has(id)) byItem.set(id, []);
121
+ byItem.get(id).push(row);
122
+ }
123
+ for (const list of byItem.values()) list.sort((a, b) => String(a.fields.marked_at ?? '').localeCompare(String(b.fields.marked_at ?? '')));
124
+ return byItem;
125
+ }
126
+
127
+ // Movement, read at question time and never stored (D3 rule 5). The three
128
+ // helpers below are what the "In flight" view computes movement with. Slice L4
129
+ // calls the same three for part 3 of the six-part answer, so one rule about
130
+ // what counts as movement serves both views.
131
+
132
+ // Every repo in view, its recent pull requests, read once and shared by every
133
+ // item. A repo GitHub could not read is named; the view still answers from the
134
+ // graph (review H11).
135
+ export async function readPullsByRepo(repoIds, readGitHub, perRepo = PRS_PER_REPO) {
136
+ const pullsByRepo = new Map(), repoErrors = [];
137
+ for (const repo of repoIds) {
138
+ try {
139
+ const rows = await readGitHub(`repos/${repo}/pulls?state=all&sort=updated&direction=desc&per_page=${perRepo}`);
140
+ pullsByRepo.set(repo, (Array.isArray(rows) ? rows : []).map((pr) => ({
141
+ repo, number: pr.number, url: pr.html_url ?? `https://github.com/${repo}/pull/${pr.number}`, title: oneLine(pr.title ?? '', 200),
142
+ branch: pr.head?.ref ?? '', merged_at: pr.merged_at ?? null, updated_at: pr.updated_at ?? pr.created_at ?? '',
143
+ state: pr.merged_at ? 'merged' : (pr.state ?? 'open'), names: `${pr.title ?? ''}\n${pr.body ?? ''}\n${pr.head?.ref ?? ''}`,
144
+ })));
145
+ } catch (error) { pullsByRepo.set(repo, []); repoErrors.push({ repo, reason: `GitHub did not answer: ${error?.message ?? String(error)}` }); }
146
+ }
147
+ return { pullsByRepo, repoErrors };
148
+ }
149
+
150
+ // A pull request belongs to an item when the item links it (item_link, kind
151
+ // pr) or when its title, body or branch names the item id (R1).
152
+ export function pullsOfItem(item, pullsByRepo, fallbackRepos = []) {
153
+ const fields = item.fields ?? {};
154
+ const linked = new Set((Array.isArray(fields.links) ? fields.links : []).filter((link) => link?.kind === 'pr').map((link) => link.url));
155
+ const mine = [];
156
+ for (const repo of (Array.isArray(fields.repos) ? fields.repos : fallbackRepos)) {
157
+ for (const pull of pullsByRepo.get(repo) ?? []) {
158
+ const how = linked.has(pull.url) ? 'link' : (pull.names.includes(item.id) ? 'names the item id' : null);
159
+ if (how) mine.push({ ...pull, names: undefined, matched_by: how });
160
+ }
161
+ }
162
+ return mine;
163
+ }
164
+
165
+ // The last thing that moved on an item: its own dates, its marks, its legacy
166
+ // seal and its pull requests, newest first.
167
+ export function movementOf({ item, marks = [], pulls = [], legacy = null, readAt }) {
168
+ const fields = item.fields ?? {};
169
+ const moments = [
170
+ { at: fields.locked_at, source: 'locked' }, { at: fields.stage_changed_at, source: `stage ${fields.stage ?? ''}` },
171
+ { at: fields.resumed_at, source: 'resumed' }, { at: fields.parked_at, source: 'parked' },
172
+ { at: fields.delivered_at, source: 'delivered' }, { at: fields.reopened_at, source: 'reopened' },
173
+ ...marks.map((mark) => ({ at: mark.fields?.marked_at, source: `${mark.fields?.kind} mark` })),
174
+ ...(legacy ? [{ at: legacy.marked_at, source: 'legacy seal' }] : []),
175
+ ...pulls.map((pull) => ({ at: pull.merged_at ?? pull.updated_at, source: `${pull.state} #${pull.number}` })),
176
+ { at: item.updated_at, source: 'record' }, { at: item.created_at, source: 'created' },
177
+ ].filter((moment) => typeof moment.at === 'string' && moment.at && !Number.isNaN(Date.parse(moment.at)));
178
+ moments.sort((a, b) => String(b.at).localeCompare(String(a.at)));
179
+ const last = moments[0] ?? null;
180
+ return { last, days: last ? daysBetween(last.at, readAt) : null, moments };
181
+ }
182
+
183
+ // A reopen corrects the delivery it names. The first reopen of a delivery is
184
+ // the correction; a later one is refused by the verb, so an older record with
185
+ // two is read this way too (review H7).
186
+ export function correctionsOf(marks = []) {
187
+ const correctedBy = new Map();
188
+ for (const mark of marks) {
189
+ const corrects = mark.fields?.corrects;
190
+ if (mark.fields?.kind === 'reopen' && typeof corrects === 'string' && !correctedBy.has(corrects)) correctedBy.set(corrects, mark.id);
191
+ }
192
+ return correctedBy;
193
+ }
194
+
195
+ export function createDeliveryVerbs(core) {
196
+ const { client, label, now, typeNamed, registry, requireProduct, requireItem, ownerPresent, patch, create, edge, landed, alreadyPatched, readGitHub, readLive } = core;
197
+
198
+ const github = async (path) => {
199
+ if (typeof readGitHub !== 'function') refuse('this verb needs a GitHub reader; the server supplies one');
200
+ try { return await readGitHub(path); }
201
+ catch (error) { refuse(`GitHub could not read ${path}: ${error?.message ?? 'unknown error'}`); }
202
+ };
203
+ const live = async (target, name) => {
204
+ if (typeof readLive !== 'function') refuse('this verb needs a live reader; the server supplies one');
205
+ try { return await readLive(target); }
206
+ catch (error) { refuse(`${name} did not answer: ${error?.message ?? 'unknown error'}`); }
207
+ };
208
+ // The reopen door (R9). The owner-present flag, or the lead in this session.
209
+ const requireOwnerOrLead = (verb) => { if (!ownerPresent && label.agent !== 'lead') refuse(`${verb} is for the owner or the lead in session; this session is agent "${label.agent}"`); };
210
+
211
+ const markOf = async (id, itemId) => {
212
+ const type = await typeNamed('mark');
213
+ let entity;
214
+ try { entity = await client.getEntity(id); }
215
+ catch (error) { if (error?.status === 404) refuse(`unknown mark id: ${id}`); throw error; }
216
+ if (entity.type_id !== type.id) refuse(`entity ${id} is not a delivery or reopen mark`);
217
+ if (itemId && entity.fields?.item !== itemId) refuse(`mark ${id} belongs to item ${entity.fields?.item}, not ${itemId}`);
218
+ return entity;
219
+ };
220
+
221
+ async function marksOf(itemIds) {
222
+ const type = await typeNamed('mark');
223
+ return marksByItem(await client.listAllEntities({ type: type.name, state: 'active' }), itemIds);
224
+ }
225
+
226
+ // ---------------------------------------------------------------------
227
+ // The three live checks. Every one is a GET. They all run before any write,
228
+ // so a failed check leaves nothing behind.
229
+ // ---------------------------------------------------------------------
230
+ function functionalSpec(value) {
231
+ if (!value || typeof value !== 'object' || Array.isArray(value)) refuse('functional is required: { url, contains, status? }. The verb GETs that URL and checks the answer.');
232
+ for (const key of ['method', 'body', 'headers', 'data', 'json']) if (key in value) refuse(`functional.${key} is not accepted; the live checks are GET only and send no body`);
233
+ const target = url(value.url, 'functional.url');
234
+ // A check that asserts nothing is not a check. It must say what the answer
235
+ // has to carry, or the verb would pass on any 200.
236
+ if (value.contains === undefined) refuse('functional.contains is required: name at least one string the answer must carry, so the check says something about the change');
237
+ const contains = stringList(value.contains, 'functional.contains', { max: 16 }).map((entry) => text(entry, 'functional.contains', { max: 500 }));
238
+ if (!contains.length) refuse('functional.contains must name at least one string the answer must carry');
239
+ let status = 200;
240
+ if (value.status !== undefined) {
241
+ // A delivery is proven by an answer, not by an error. 404 is not a pass.
242
+ if (!Number.isInteger(value.status) || value.status < 200 || value.status > 299) refuse('functional.status must be a 2xx status; a delivery is proven by an answer, not by an error');
243
+ status = value.status;
244
+ }
245
+ return { url: target, contains, status };
246
+ }
247
+
248
+ // Where the functional check may go. The owner writes check_paths on the
249
+ // product; without it the verb may read only the health endpoint's own path.
250
+ // The check runs with the Engram credential, so an open origin would let any
251
+ // in-session agent read any path and learn from the refusal what it holds.
252
+ function allowedPath(spec, product, healthUrl) {
253
+ const health = new URL(healthUrl);
254
+ const target = new URL(spec.url);
255
+ if (target.origin !== health.origin) refuse(`functional.url must be on the same origin as the product's health endpoint (${health.origin}); the check runs against the deployed app, not another host`);
256
+ const allowed = Array.isArray(product.fields?.check_paths) ? product.fields.check_paths.filter((path) => typeof path === 'string' && path.startsWith('/')) : [];
257
+ const paths = allowed.length ? allowed : [health.pathname];
258
+ const under = (prefix) => target.pathname === prefix || target.pathname.startsWith(prefix.endsWith('/') ? prefix : `${prefix}/`);
259
+ if (!paths.some(under)) refuse(`functional.url path ${target.pathname} is not one the owner opened for checks on product ${product.fields.registry_id}. Allowed: ${paths.join(', ')}. The owner adds a path with product edit, check_paths.`);
260
+ // The path alone decides. An earlier draft compared the query too, so
261
+ // /healthz?probe=1 passed as a functional check (review H15).
262
+ if (target.pathname === health.pathname) refuse('functional.url must not be the health endpoint itself; the functional check is scoped to the change, and the health endpoint is check two');
263
+ }
264
+
265
+ async function checkHealth(healthUrl) {
266
+ const answer = await live(healthUrl, 'the health endpoint');
267
+ const ok = answer.status >= 200 && answer.status < 300;
268
+ const found = ok ? deployedCommitOf(answer.body) : null;
269
+ return {
270
+ name: 'health-endpoint-answers', result: ok && found ? 'pass' : 'fail', url: healthUrl, status: answer.status,
271
+ ...(found ? { deployed_commit: found.commit, reported: found.reported, field: found.field } : {}),
272
+ detail: !ok ? `the health endpoint answered ${answer.status}` : (found ? `${healthUrl} answered ${answer.status}; deployed commit ${found.commit}` : 'the health endpoint answered, but named no commit in commit, sha, revision, version, build or git_sha'),
273
+ checked_at: now(),
274
+ };
275
+ }
276
+
277
+ async function checkContains(pulls, deployed) {
278
+ const detail = [];
279
+ let result = 'pass';
280
+ for (const pull of pulls) {
281
+ const compare = await github(`repos/${pull.repo}/compare/${pull.merge_commit_sha}...${deployed}`);
282
+ const status = compare?.status ?? 'unknown';
283
+ const contains = status === 'identical' || status === 'ahead';
284
+ if (!contains) result = 'fail';
285
+ detail.push({ pr: pull.url, merge_commit_sha: pull.merge_commit_sha, deployed, compare: status, behind_by: compare?.behind_by ?? null, contains });
286
+ }
287
+ return { name: 'deployed-commit-contains-the-prs', result, deployed_commit: deployed, prs: detail, checked_at: now(),
288
+ detail: result === 'pass' ? `the deployed commit ${deployed} carries all ${pulls.length} merged pull request${pulls.length === 1 ? '' : 's'}` : `the deployed commit ${deployed} does not carry ${detail.filter((row) => !row.contains).map((row) => row.pr).join(', ')}` };
289
+ }
290
+
291
+ async function checkFunctional(spec, healthUrl) {
292
+ const answer = await live(spec.url, 'the functional check');
293
+ const body = String(answer.body ?? '');
294
+ const missing = spec.contains.filter((needle) => !body.includes(needle));
295
+ const statusOk = answer.status === spec.status;
296
+ const result = statusOk && !missing.length ? 'pass' : 'fail';
297
+ return {
298
+ name: 'read-only-functional-check', result, url: spec.url, method: 'GET', status: answer.status, expected_status: spec.status,
299
+ // The answer itself never enters the graph: a digest, its size, and which
300
+ // strings the caller asked for were found.
301
+ body_sha256: sha256(body), body_bytes: Buffer.byteLength(body), contains: spec.contains, missing,
302
+ detail: result === 'pass' ? `GET ${spec.url} answered ${answer.status}${spec.contains.length ? ` and carried ${spec.contains.length} expected string${spec.contains.length === 1 ? '' : 's'}` : ''}` : (!statusOk ? `GET ${spec.url} answered ${answer.status}, not ${spec.status}` : `GET ${spec.url} did not carry: ${missing.join(', ')}`),
303
+ checked_at: now(),
304
+ };
305
+ }
306
+
307
+ const verbs = {
308
+ // The delivering agent marks its own change done, after the verb runs the
309
+ // three live checks itself. No owner flag: the agent that shipped it calls
310
+ // this, and the lead catches up a delivery made outside a session (R2).
311
+ async item_done(args, requestId, meta) {
312
+ const item = await requireItem(args.item_id);
313
+ const fields = item.fields ?? {};
314
+ const replay = alreadyPatched(item, requestId, meta);
315
+ if (!replay && fields.lifecycle !== 'active') refuse(`item ${item.id} is ${fields.lifecycle}; only an active item is marked done`);
316
+ const reg = await registry();
317
+ const product = await requireProduct(fields.product, reg);
318
+ // A retry of an interrupted write finishes what landed, and it is read
319
+ // FIRST, before any refusal that depends on registry state. A repo kind
320
+ // edited between two attempts would otherwise send the retry down the
321
+ // strict path, refuse it for a missing health endpoint, and strand the
322
+ // mark this same request already wrote (architect review, finding 8).
323
+ // It runs no check again: the checks proved THIS delivery when the mark
324
+ // was written, and a check that has since gone red would otherwise
325
+ // strand a mark the item never points at (reviews G1 and G2 of Batch 11,
326
+ // finding H5 of Batch 13).
327
+ const type = await typeNamed('mark');
328
+ const standing = await landed(type, requestId, meta);
329
+ if (standing) {
330
+ await edge(standing, item, 'references', requestId);
331
+ const held = Array.isArray(fields.delivery_marks) ? fields.delivery_marks : [];
332
+ const finished = held.includes(standing.id) ? item : await patch(item, 'item_done', requestId, {
333
+ lifecycle: 'done', delivery_state: 'delivered', delivered_at: standing.fields.marked_at, delivery_marks: [...held, standing.id],
334
+ }, { digest: meta?.digest });
335
+ return { verb: 'item_done', item_id: finished.id, version: finished.version, lifecycle: finished.fields.lifecycle, mark_id: standing.id,
336
+ delivered_at: standing.fields.marked_at, prs: (standing.fields.prs ?? []).map((pull) => pull.url), deployed_commit: standing.fields.deployed?.commit ?? null,
337
+ checks: (standing.fields.checks ?? []).map((check) => ({ name: check.name, result: check.result, detail: check.detail })),
338
+ verified_by: (standing.fields.verified_by ?? []).map((row) => ({ pr: row.pr, passed: row.passed })), gate_warnings: standing.fields.gate_warnings ?? [],
339
+ replayed_from_graph: true };
340
+ }
341
+
342
+ // F4, ordered by the owner: what proves a delivery follows the repo,
343
+ // not one product's shape. Amendment A1.7.
344
+ //
345
+ // Only a `library` is relaxed. A library's merge IS its delivery: there
346
+ // is nothing after it. A `schema` repo is the opposite: this system's
347
+ // own release procedure applies a migration to the node AFTER the merge,
348
+ // so a merge-proved "delivered" would be false about production. A
349
+ // `schema` repo therefore stays on the strict path with `service`
350
+ // (architect review of this batch, finding 1).
351
+ //
352
+ // The strict path is also the default: an item with no registered repo,
353
+ // or a repo whose kind was never set, keeps the three checks. Nothing
354
+ // weakens by omission, and a mixed-kind item takes the strict path
355
+ // because one of its repos demands it.
356
+ const named = (Array.isArray(fields.repos) ? fields.repos : []).map((id) => reg.repos.get(id)).filter(Boolean);
357
+ const unset = named.filter((repo) => !repo.fields?.kind).map((repo) => repo.fields.registry_id);
358
+ const strictKinds = named.filter((repo) => repo.fields?.kind && repo.fields.kind !== 'library').map((repo) => `${repo.fields.registry_id} is a ${repo.fields.kind}`);
359
+ const live = !named.length || unset.length > 0 || strictKinds.length > 0;
360
+ const healthUrl = text(product.fields?.health_url, `product ${product.fields.registry_id} health_url`, { optional: true, max: 2000 });
361
+ if (live && !healthUrl) {
362
+ const why = !named.length ? 'the item names no registered repo' : (unset.length ? `repo ${unset.join(', ')} carries no kind` : `${strictKinds.join(', ')}`);
363
+ refuse(`product ${product.fields.registry_id} has no health_url, and the live checks apply because ${why}; the owner sets it with product edit, or sets the repo kind to library, which is the one kind whose merge is its delivery`);
364
+ }
365
+ // Which pull requests deliver this item. The caller names them, always.
366
+ // An earlier draft fell back to the item's own pr links, and the first
367
+ // live run marked the wrong item done: that item's one pr link was the
368
+ // pull request that LOCKED its design, with role "lock". A link says a
369
+ // pull request belongs to the item; it does not say the item shipped.
370
+ // The delivering agent knows what it delivered, so it says so.
371
+ if (args.prs === undefined) refuse('prs is required: name the merged pull requests this delivery shipped. The item\'s pr links are not read, because a link can be a design or a lock, not a delivery.');
372
+ const given = stringList(args.prs, 'prs', { max: MAX_PRS });
373
+ if (!given.length) refuse('prs must name at least one merged pull request');
374
+ let spec = null;
375
+ if (live) { spec = functionalSpec(args.functional); allowedPath(spec, product, healthUrl); }
376
+ else if (args.functional !== undefined) refuse(`functional is not accepted for this delivery: ${named.map((repo) => `${repo.fields.registry_id} is a ${repo.fields.kind}`).join(', ')}, so there is no deployed page to read. The merge and the gate prove it.`);
377
+ const note = text(args.note, 'note', { optional: true, max: 2000 }) ?? '';
378
+
379
+ const repos = new Set(Array.isArray(fields.repos) ? fields.repos : []);
380
+ const pulls = [];
381
+ for (const entry of given) {
382
+ const parsed = parsePullUrl(entry, 'prs');
383
+ if (!reg.repos.has(parsed.repo)) refuse(`pull request ${parsed.url} is in ${parsed.repo}, which is not a registered repo`);
384
+ if (repos.size && !repos.has(parsed.repo)) refuse(`pull request ${parsed.url} is in ${parsed.repo}, which is not one of the item's repos (${[...repos].join(', ')})`);
385
+ // The merge state comes from GitHub, never from the caller (R10).
386
+ const pr = await github(`repos/${parsed.repo}/pulls/${parsed.number}`);
387
+ if (pr?.merged !== true) refuse(`pull request ${parsed.url} is not merged on GitHub; a delivery is marked after the merge`);
388
+ const mergeCommit = text(pr.merge_commit_sha, `merge_commit_sha of ${parsed.url}`, { max: 64 });
389
+ pulls.push({ ...parsed, title: oneLine(pr.title ?? '', 200), merged_at: pr.merged_at ?? '', merge_commit_sha: mergeCommit, branch: pr.head?.ref ?? '', base: pr.base?.ref ?? '' });
390
+ }
391
+
392
+ // The three live checks, for a delivery that reaches a running service.
393
+ let health = null, contains = null, functional = null;
394
+ if (live) {
395
+ // Check one: the health endpoint answers, and says which commit is live.
396
+ health = await checkHealth(healthUrl);
397
+ if (health.result !== 'pass') refuse(`the live check failed: ${health.detail}`);
398
+ // Check two: that commit carries every pull request.
399
+ contains = await checkContains(pulls, health.deployed_commit);
400
+ if (contains.result !== 'pass') refuse(`the live check failed: ${contains.detail}`);
401
+ // Check three: a read-only functional check scoped to the change.
402
+ functional = await checkFunctional(spec, healthUrl);
403
+ if (functional.result !== 'pass') refuse(`the live check failed: ${functional.detail}`);
404
+ }
405
+
406
+ // The gate run, fetched from GitHub for each merge commit (R10).
407
+ const verified = [], warnings = [];
408
+ for (const pull of pulls) {
409
+ const answer = await github(`repos/${pull.repo}/commits/${pull.merge_commit_sha}/check-runs?per_page=${CHECK_RUNS}`);
410
+ const runs = latestCheckRuns(answer?.check_runs);
411
+ const failed = runs.filter((run) => FATAL_CONCLUSIONS.includes(run.conclusion ?? ''));
412
+ const passed = runs.filter((run) => run.conclusion === PASSING_CONCLUSION);
413
+ if (failed.length) refuse(`the merge gate is red for ${pull.url}: ${failed.map((run) => `${run.name} ${run.conclusion} (${run.url})`).join(', ')}`);
414
+ if (!passed.length) refuse(`no check run succeeded for ${pull.url}; the merge gate is the independent record and it says nothing`);
415
+ // A check name whose LATEST run succeeded is green. A name whose earlier
416
+ // run was red and whose later run was skipped or neutral is not red and
417
+ // not green: the verb cannot tell a re-run that fixed it from a gate
418
+ // that was switched off. It does not guess. It marks the delivery and
419
+ // writes the warning on the mark, where the owner reads it and can
420
+ // reopen. The lock text does not set a gate policy; this is the lead's
421
+ // rule for M1, and it is recorded, never silent.
422
+ for (const run of runs) {
423
+ if (!run.superseded_failures || run.conclusion === PASSING_CONCLUSION) continue;
424
+ const last = run.superseded_failures.at(-1);
425
+ warnings.push({ pr: pull.url, check: run.name, earlier: last.conclusion, earlier_url: last.url, latest: run.conclusion, latest_url: run.url,
426
+ detail: `${run.name} concluded ${last.conclusion} for ${pull.url}, and its later run concluded ${run.conclusion}, not success. The verb cannot tell a fixed gate from a gate that was switched off.` });
427
+ }
428
+ verified.push({ pr: pull.url, commit: pull.merge_commit_sha, runs, passed: passed.map((run) => run.name) });
429
+ }
430
+
431
+ // One row per check that ran. For a repo with no deploy target the row
432
+ // says what was proven instead: every named pull request is merged, and
433
+ // the gate below is green. The merge commits are on the mark's prs.
434
+ const checks = live ? [contains, health, functional] : [{
435
+ name: 'every named pull request is merged',
436
+ result: 'pass',
437
+ detail: `${pulls.length} pull request${pulls.length === 1 ? '' : 's'} merged on GitHub: ${pulls.map((pull) => `${pull.url} at ${pull.merge_commit_sha.slice(0, 7)}`).join(', ')}. No live check ran: ${named.map((repo) => `${repo.fields.registry_id} is a ${repo.fields.kind}`).join(', ')}, so the product has no deployed endpoint to read (amendment A1.7).`,
438
+ checked_at: now(),
439
+ }];
440
+ const mark = await create(type, requestId, 'item_done', {
441
+ digest: meta?.digest,
442
+ title: oneLine(`Delivered · ${item.title} · ${pulls.map((pull) => `#${pull.number}`).join(' ')}`, 200),
443
+ body: checks.map((check) => `${check.name}: ${check.result} — ${check.detail}`).join('\n'),
444
+ fields: {
445
+ kind: 'delivery', verdict: 'delivered', item: item.id, product: product.fields.registry_id, marked_at: now(),
446
+ prs: pulls.map((pull) => ({ url: pull.url, repo: pull.repo, number: pull.number, title: pull.title, branch: pull.branch, merged_at: pull.merged_at, merge_commit_sha: pull.merge_commit_sha })),
447
+ ...(live ? { deployed: { health_url: healthUrl, commit: health.deployed_commit, reported: health.reported, status: health.status, checked_at: health.checked_at } } : {}),
448
+ checks, verified_by: verified, ...(warnings.length ? { gate_warnings: warnings } : {}), ...(note ? { note } : {}),
449
+ },
450
+ tags: ['atlas-mark', `product:${product.fields.registry_id}`, ...pulls.map((pull) => `repo:${pull.repo}`)],
451
+ });
452
+ await edge(mark, item, 'references', requestId);
453
+ const marks = Array.isArray(fields.delivery_marks) ? fields.delivery_marks : [];
454
+ const patched = replay ? item : await patch(item, 'item_done', requestId, {
455
+ lifecycle: 'done', delivery_state: 'delivered', delivered_at: mark.fields.marked_at,
456
+ delivery_marks: marks.includes(mark.id) ? marks : [...marks, mark.id],
457
+ }, { digest: meta?.digest });
458
+ return {
459
+ verb: 'item_done', item_id: patched.id, version: patched.version, lifecycle: patched.fields.lifecycle, mark_id: mark.id,
460
+ delivered_at: mark.fields.marked_at, prs: pulls.map((pull) => pull.url), deployed_commit: health?.deployed_commit ?? null,
461
+ proof: live ? 'live-checks' : 'merge-and-gate',
462
+ checks: checks.map((check) => ({ name: check.name, result: check.result, detail: check.detail })),
463
+ verified_by: verified.map((row) => ({ pr: row.pr, passed: row.passed })), gate_warnings: warnings,
464
+ ...(replay ? { replayed_from_graph: true } : {}),
465
+ };
466
+ },
467
+
468
+ // Reopen: the owner or the lead in session, with a reason and the failed
469
+ // check as a URL. It appends a mark that corrects the delivery. The
470
+ // delivery mark is never touched (R9).
471
+ async item_reopen(args, requestId, meta) {
472
+ requireOwnerOrLead('item reopen');
473
+ const item = await requireItem(args.item_id);
474
+ const fields = item.fields ?? {};
475
+ const reason = text(args.reason, 'reason', { max: 4000 });
476
+ const failedCheck = url(args.failed_check, 'failed_check');
477
+ if (alreadyPatched(item, requestId, meta)) return { verb: 'item_reopen', item_id: item.id, version: item.version, lifecycle: fields.lifecycle, delivery_state: fields.delivery_state ?? null, reason, replayed_from_graph: true };
478
+ const marks = Array.isArray(fields.delivery_marks) ? fields.delivery_marks : [];
479
+ if (!marks.length) refuse(`item ${item.id} carries no delivery mark; there is nothing to reopen`);
480
+ // One reopen per delivery. A delivery that is already corrected cannot be
481
+ // corrected again: the second live run of Batch 12 stacked two reopens on
482
+ // one delivery because the default kept choosing the same mark.
483
+ const held = [];
484
+ for (const id of marks) held.push(await markOf(id, item.id));
485
+ const alreadyCorrected = new Map();
486
+ for (const mark of held) if (mark.fields?.kind === 'reopen' && typeof mark.fields.corrects === 'string' && !alreadyCorrected.has(mark.fields.corrects)) alreadyCorrected.set(mark.fields.corrects, mark.id);
487
+ let corrected;
488
+ if (args.corrects !== undefined) corrected = await markOf(text(args.corrects, 'corrects', { max: 200 }), item.id);
489
+ else corrected = [...held].reverse().find((mark) => mark.fields?.kind === 'delivery' && !alreadyCorrected.has(mark.id));
490
+ if (!corrected) refuse(`item ${item.id} has no standing delivery mark to correct; every delivery on it is already corrected`);
491
+ if (corrected.fields?.kind !== 'delivery') refuse(`mark ${corrected.id} is a ${corrected.fields?.kind} mark; a reopen corrects a delivery mark`);
492
+ if (alreadyCorrected.has(corrected.id)) refuse(`delivery mark ${corrected.id} is already corrected by ${alreadyCorrected.get(corrected.id)}; mark the item done again before reopening it again`);
493
+ const type = await typeNamed('mark');
494
+ const prior = await landed(type, requestId, meta);
495
+ const mark = prior ?? await create(type, requestId, 'item_reopen', {
496
+ digest: meta?.digest, title: oneLine(`Reopened · ${item.title}`, 200), body: `${reason}\n\nFailed check: ${failedCheck}\nCorrects delivery mark ${corrected.id}.`,
497
+ fields: {
498
+ kind: 'reopen', verdict: 'reopened', item: item.id, product: fields.product ?? '', marked_at: now(),
499
+ reason, failed_check: failedCheck, corrects: corrected.id,
500
+ },
501
+ tags: ['atlas-mark', ...(fields.product ? [`product:${fields.product}`] : [])],
502
+ });
503
+ // The corrects edge of 0.2.0, from the reopen to the delivery it corrects.
504
+ await edge(mark, corrected, 'corrects', requestId);
505
+ await edge(mark, item, 'references', requestId);
506
+ const patched = await patch(item, 'item_reopen', requestId, {
507
+ lifecycle: 'active', delivery_state: 'reopened', reopened_at: mark.fields.marked_at,
508
+ delivery_marks: marks.includes(mark.id) ? marks : [...marks, mark.id],
509
+ }, { digest: meta?.digest });
510
+ return { verb: 'item_reopen', item_id: patched.id, version: patched.version, lifecycle: 'active', delivery_state: 'reopened', mark_id: mark.id, corrects: corrected.id, failed_check: failedCheck, reason, ...(prior ? { replayed_from_graph: true } : {}) };
511
+ },
512
+
513
+ // D3: the "In flight" view. Computed at question time, per product, never
514
+ // stored. Movement comes from the marks and from the repos' pull requests.
515
+ async in_flight(args = {}) {
516
+ const reg = await registry();
517
+ let only = null;
518
+ if (args.product !== undefined) only = (await requireProduct(args.product, reg)).fields.registry_id;
519
+ const since = args.since === undefined ? null : isoDate(args.since, 'since');
520
+ const container = await typeNamed('container');
521
+ const all = await client.listAllEntities({ type: container.name, state: 'active' });
522
+ const registered = (row) => typeof row.fields?.product === 'string' && reg.products.has(row.fields.product);
523
+ const items = all.filter((row) => registered(row) && (!only || row.fields.product === only));
524
+ // D3 rule 2: a row names its product. An item with no registered product
525
+ // cannot be a row, so it is named here instead of dropped in silence.
526
+ // Slice L5's sweep is what gives these items a product.
527
+ const unnamed = all.filter((row) => !registered(row)).map((row) => ({
528
+ item: row.id, title: oneLine(row.title, 120), lifecycle: row.fields?.lifecycle ?? 'unknown',
529
+ ...(row.fields?.sealed_at ? { legacy_seal: { sealed_at: row.fields.sealed_at, verdict: row.fields.seal_verdict ?? 'unknown' } } : {}),
530
+ reason: typeof row.fields?.product === 'string' && row.fields.product ? `product "${row.fields.product}" is not in the registry` : 'the item names no product; the slice L5 sweep gives it one',
531
+ }));
532
+ // The tenant holds dozens of these. The view keeps a bounded sample, the
533
+ // sealed ones first, and reports the true count (review H16).
534
+ // A product-scoped read gets the count and no rows: an item with no
535
+ // product cannot belong to the product the reader asked about.
536
+ const unregistered = only ? [] : [...unnamed.filter((row) => row.legacy_seal), ...unnamed.filter((row) => !row.legacy_seal)].slice(0, UNREGISTERED_KEPT);
537
+ const unregisteredTotal = unnamed.length;
538
+ if (items.length > MAX_ITEMS) refuse(`${items.length} items are registered; the view reads at most ${MAX_ITEMS}. Narrow it with a product id.`);
539
+ const byId = new Map(items.map((row) => [row.id, row]));
540
+ const marks = await marksOf(new Set(byId.keys()));
541
+
542
+ // Movement from git: every registered repo of the products in view, its
543
+ // recent pull requests, once per repo and shared by every item.
544
+ const repoIds = [...reg.repos.values()].filter((repo) => !only || repo.fields.product === only).map((repo) => repo.fields.registry_id).sort();
545
+ if (repoIds.length > MAX_REPOS) refuse(`${repoIds.length} repos are registered for this view; it reads at most ${MAX_REPOS}. Narrow it with a product id.`);
546
+ const { pullsByRepo, repoErrors } = await readPullsByRepo(repoIds, readGitHub);
547
+
548
+ const readAt = now();
549
+ const rows = [], delivered = [], notMarked = [];
550
+ for (const item of items) {
551
+ const fields = item.fields ?? {};
552
+ const own = marks.get(item.id) ?? [];
553
+ // R14: only a delivery mark replaces a legacy seal. A reopen mark, or
554
+ // any later mark kind, must not erase it (review H9, batch 13).
555
+ const legacy = own.some((mark) => mark.fields?.kind === 'delivery') ? null : legacySealOf(item);
556
+ // A reopen corrects the delivery it names. The corrected delivery stays
557
+ // in the graph and stays visible here, but it no longer stands: its
558
+ // pull requests go back to "merged, not marked" (design finding R9).
559
+ const correctedBy = correctionsOf(own);
560
+ const deliveries = own.filter((mark) => mark.fields.kind === 'delivery');
561
+ const standing = deliveries.filter((mark) => !correctedBy.has(mark.id));
562
+ const mine = pullsOfItem(item, pullsByRepo, repoIds);
563
+ const markedUrls = new Set(standing.flatMap((mark) => (mark.fields.prs ?? []).map((pull) => pull.url)));
564
+ const merged = mine.filter((pull) => pull.state === 'merged');
565
+ // R2: a delivery made outside a session shows as "merged, not marked"
566
+ // until the lead catches it up with the same done verb.
567
+ const open = merged.filter((pull) => !markedUrls.has(pull.url));
568
+ const { last, days } = movementOf({ item, marks: own, pulls: mine, legacy, readAt });
569
+ const state = fields.lifecycle === 'done' ? 'delivered' : (fields.delivery_state === 'reopened' ? 'reopened' : fields.lifecycle);
570
+
571
+ const inScope = fields.lifecycle === 'active' || fields.lifecycle === 'done';
572
+ for (const pull of (inScope ? open : [])) notMarked.push({ item: item.id, item_title: oneLine(item.title, 120), product: fields.product, pr: pull.url, title: pull.title, merged_at: pull.merged_at, days: daysBetween(pull.merged_at, readAt), matched_by: pull.matched_by, note: 'merged, not marked' });
573
+ for (const mark of deliveries) if (!since || String(mark.fields.marked_at ?? '') >= since) delivered.push({ item: item.id, item_title: oneLine(item.title, 120), product: fields.product, mark_id: mark.id, verdict: correctedBy.has(mark.id) ? 'corrected' : (mark.fields.verdict ?? 'delivered'), ...(correctedBy.has(mark.id) ? { corrected_by: correctedBy.get(mark.id) } : {}), delivered_at: mark.fields.marked_at, prs: (mark.fields.prs ?? []).map((pull) => pull.url), deployed_commit: mark.fields.deployed?.commit ?? null, gate_warnings: mark.fields.gate_warnings ?? [] });
574
+ if (legacy && (!since || String(legacy.marked_at) >= since)) delivered.push({ item: item.id, item_title: oneLine(item.title, 120), product: fields.product, mark_id: null, verdict: 'legacy', delivered_at: legacy.marked_at, prs: [], deployed_commit: null, source: legacy.source });
575
+
576
+ // D3 rule 1: in flight is active work. Delivered and paused items leave
577
+ // the rows; what was delivered since a date has its own list.
578
+ if (fields.lifecycle !== 'active') continue;
579
+ rows.push({
580
+ item: item.id, product: fields.product, stage: fields.stage ?? '', driver: fields.driver ?? '', days_since_last_change: days,
581
+ last_change_at: last?.at ?? null, last_change_source: last?.source ?? 'nothing recorded', delivery_state: state,
582
+ lines: [
583
+ // The id is what a reader types back, so the title gives way to it
584
+ // inside the line budget, as the "Needs me" row does (review G6).
585
+ oneLine(`${oneLine(item.title, Math.max(1, ROW_LINE_MAX - item.id.length - 3))} · ${item.id}`),
586
+ oneLine(`${fields.product} · ${fields.stage ?? 'no stage'} · ${fields.driver ?? 'no driver'} · next: ${fields.next_step || 'not set'}`),
587
+ oneLine(`${days === null ? 'no movement on record' : `${days} day${days === 1 ? '' : 's'} since last change`} (${last?.source ?? 'nothing recorded'}) · ${mine.length} pull request${mine.length === 1 ? '' : 's'}${open.length ? ` · ${open.length} merged, not marked` : ''}`),
588
+ ],
589
+ folded: { title: item.title, next_step: fields.next_step ?? '', scope: fields.scope?.text ?? '', pieces: fields.split_children ?? [], prs: mine.map((pull) => ({ url: pull.url, state: pull.state, matched_by: pull.matched_by, merged_at: pull.merged_at, updated_at: pull.updated_at })), marks: own.map((mark) => ({ id: mark.id, kind: mark.fields.kind, verdict: mark.fields.verdict, at: mark.fields.marked_at })) },
590
+ citation: { item: item.id, read_at: readAt, repos: [...new Set(mine.map((pull) => pull.repo))] },
591
+ });
592
+ }
593
+ // The owner's rule: order by days since last change, latest on top, and
594
+ // show the days on the row.
595
+ rows.sort((a, b) => (a.days_since_last_change ?? Number.MAX_SAFE_INTEGER) - (b.days_since_last_change ?? Number.MAX_SAFE_INTEGER) || a.item.localeCompare(b.item));
596
+ delivered.sort((a, b) => String(b.delivered_at ?? '').localeCompare(String(a.delivered_at ?? '')));
597
+ notMarked.sort((a, b) => (a.days ?? 0) - (b.days ?? 0));
598
+ const products = [...new Set(rows.map((row) => row.product))].sort();
599
+ const byProduct = products.map((product) => ({ product, rows: rows.filter((row) => row.product === product) }));
600
+ const block = byProduct.map((group) => [`${group.product} — in flight (${group.rows.length})`, ...group.rows.flatMap((row, index) => row.lines.map((line, position) => `${position === 0 ? `${index + 1}. ` : ' '}${line}`))].join('\n'));
601
+ if (notMarked.length) block.push([`merged, not marked (${notMarked.length})`, ...notMarked.map((row) => listLine(row.title, `${row.pr} · ${row.item} · merged ${row.days} day${row.days === 1 ? '' : 's'} ago`))].join('\n'));
602
+ if (repoErrors.length) block.push([`GitHub did not answer for ${repoErrors.length} repo${repoErrors.length === 1 ? '' : 's'}; movement below is from the graph only`, ...repoErrors.map((row) => oneLine(` ${row.repo} — ${row.reason}`, ROW_LINE_MAX))].join('\n'));
603
+ if (unregisteredTotal && !only) {
604
+ // The list can be long, and a long list is not a view. The sealed ones
605
+ // come first, because a legacy seal is a delivery the owner may want to
606
+ // see; the rest are a count and a pointer to the slice L5 sweep.
607
+ const sealed = unnamed.filter((row) => row.legacy_seal);
608
+ const shown = unregistered.slice(0, UNREGISTERED_SHOWN);
609
+ block.push([
610
+ `not in the registry (${unregisteredTotal}${sealed.length ? `, ${sealed.length} with a legacy seal` : ''}); the slice L5 sweep gives them a product`,
611
+ ...shown.map((row) => listLine(row.title, `${row.item} · ${row.legacy_seal ? `legacy seal ${String(row.legacy_seal.sealed_at).slice(0, 10)}, verdict ${row.legacy_seal.verdict}` : row.lifecycle}`)),
612
+ ...(unregisteredTotal > shown.length ? [` and ${unregisteredTotal - shown.length} more, ${unregistered.length - shown.length} of them in this answer`] : []),
613
+ ].join('\n'));
614
+ }
615
+ if (since && delivered.length) block.push([`delivered since ${since} (${delivered.length})`, ...delivered.map((row) => listLine(row.item_title, `${row.verdict}${(row.gate_warnings ?? []).length ? `, gate warning on ${row.gate_warnings.map((warning) => warning.check).join(' ')}` : ''} · ${String(row.delivered_at ?? '').slice(0, 10)} · ${row.prs.join(' ') || 'no pull request on the mark'}`))].join('\n'));
616
+ return {
617
+ verb: 'in_flight', read_at: readAt, product: only, since, rows: rows.length, products: byProduct,
618
+ merged_not_marked: notMarked, delivered: since ? delivered : delivered.slice(0, 20), unregistered, unregistered_total: unregisteredTotal, repos_read: repoIds, repo_errors: repoErrors,
619
+ text: block.join('\n\n') || 'Nothing is in flight: no active item on a registered product.',
620
+ };
621
+ },
622
+ };
623
+ return verbs;
624
+ }