omp-conductor 0.20.1 → 0.20.3

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.
@@ -22,7 +22,7 @@ import { admitCandidates, startOfToday, type AdmissionHold } from "../admission.
22
22
  import { dbBackupDirFor, findProject, loadConfig, resolveCaps, stateDir } from "../config.ts";
23
23
  import { evaluateDecisionConditions, probeNpmVersion, probeRateLimitReset } from "../decisions.ts";
24
24
  import { dbSnapshotDue, dbSnapshotMarkerKey, localDayKey } from "../digest-schedule.ts";
25
- import { runDoctor } from "../doctor.ts";
25
+ import { reconcileOrphanedPause, runDoctor } from "../doctor.ts";
26
26
  import { fleetLayers, herdrPaneOmpStarts, resolveHerdrSession } from "../fleet.ts";
27
27
  import { projectLabels } from "../label-projection.ts";
28
28
  import { healthCheck } from "../lifecycle.ts";
@@ -42,13 +42,13 @@ import { writeAdmissionAck } from "./ack.ts";
42
42
  import { dispatchAdmissions, summarizeDispatch, summarizeHeldPass, type WorkerPool } from "./admission-pass.ts";
43
43
  import { NO_ISSUE, PACKAGE_SRC_DIR, UNROUTABLE_TEXT, type Deps } from "./deps.ts";
44
44
  import { handleIssue } from "./dispatch.ts";
45
- import { cancelDrain, consumeDrain } from "./drain.ts";
45
+ import { consumeDrain, markDrained } from "./drain.ts";
46
46
  import { dispatchToSpecGrooming } from "./groom-pass.ts";
47
47
  import { INTEGRITY_SAMPLE, checkIntegrity, markPaged, packageManifest } from "./integrity.ts";
48
48
  import { reconcilePanes } from "./panes.ts";
49
49
  import { applyAdjudicationDispositions, dispatchReviewAdjudications, dispatchReviewRevisions } from "./review.ts";
50
50
  import { cleanupRetainedRuns, watchBaseHealth, watchMergedBase } from "./settle-pass.ts";
51
- import { wakeOrchestratorForMetConditions, watchOrchestrator } from "./supervision.ts";
51
+ import { wakeOrchestratorForMetConditions, watchOrchestrator, watchWorkerProgress } from "./supervision.ts";
52
52
 
53
53
  /**
54
54
  * Record what this host has installed, once per dispatch pass (#919).
@@ -388,6 +388,16 @@ export async function tick(d: Deps, workers?: WorkerPool): Promise<void> {
388
388
  // failure happened. A pause silences claiming, not the operator's right to
389
389
  // know their supervising session stopped reading its queue.
390
390
  await watchOrchestrator(d);
391
+ // The worker-side sibling, and above the pause gate with it: a stalled run
392
+ // holds its slot on a parked fleet exactly as hard as on a busy one, and the
393
+ // #1086 incident was an upgrade drain blocked for 26 minutes by one silent
394
+ // session. Paused workers are skipped inside the pass — their silence is
395
+ // the pause working (#938) — so this gate costs nothing there.
396
+ try {
397
+ await watchWorkerProgress(d);
398
+ } catch (err) {
399
+ log(`worker-progress sweep failed: ${errText(err)}`);
400
+ }
391
401
  // The down incident is reconciled the same place and for the same reason: a
392
402
  // session that has actually died is as much the operator's concern as one
393
403
  // that is wedged, and restarting it is the daemon's restart either way. This
@@ -400,6 +410,20 @@ export async function tick(d: Deps, workers?: WorkerPool): Promise<void> {
400
410
  escalate: (event) => d.escalate(event),
401
411
  log,
402
412
  });
413
+ // The orphaned-pause watch rides the same above-the-pause-gate band (#1113):
414
+ // a sentinel whose writer has exited is exactly the condition a paused
415
+ // fleet cannot surface through its own gates below, and an `upgrade` pause
416
+ // can never be cleared by any future event. The notification ledger dedupes
417
+ // the page to once per sentinel instance; resuming stays a human decision.
418
+ try {
419
+ await reconcileOrphanedPause({
420
+ project: d.project.name,
421
+ escalate: (event) => d.escalate(event),
422
+ log,
423
+ });
424
+ } catch (err) {
425
+ log(`orphaned-pause watch failed: ${errText(err)}`);
426
+ }
403
427
 
404
428
  // Beside the two orchestrator watches, and above the pause gate, for the same
405
429
  // reason they are: mining reads rows this store already holds and files intake
@@ -584,11 +608,16 @@ export async function tick(d: Deps, workers?: WorkerPool): Promise<void> {
584
608
  // the same admission boundary — settlement above it, nothing claimed below —
585
609
  // but the intent is durable (it survives orchestrator loss) and bounded (the
586
610
  // record carries an absolute deadline, so a crash can never strand
587
- // admission). Three shapes, three behaviours:
611
+ // admission). Reaching an empty active set SUCCEEDS the drain; it does not
612
+ // end it (#1078): the release window the drain exists to create opens at
613
+ // that moment and holds claims down until the deadline — exactly the quiet
614
+ // interval a tick-driven orchestrator needs to cut a release, which the old
615
+ // eight-second auto-clear destroyed before any tick could use it. Four
616
+ // shapes, four behaviours:
588
617
  // - fresh drain with live runs → a held pass, exactly like a pause;
589
- // - fresh drain with nothing left to wait for → the drain is satisfied
590
- // and clears itself, so a completed drain never needs a second operator
591
- // action and this pass proceeds normally;
618
+ // - fresh drain whose active set just reached zero → the opening is marked
619
+ // on the record once and logged once, and this pass admits nothing;
620
+ // - fresh drain already marked drained → the same held pass, silent;
592
621
  // - malformed record → fails closed for THIS pass (it might be a fresh
593
622
  // fence we cannot read), and the same consume removed it, so it can
594
623
  // never become an unbounded permanent drain.
@@ -596,15 +625,17 @@ export async function tick(d: Deps, workers?: WorkerPool): Promise<void> {
596
625
  if (drain.kind === "active") {
597
626
  // Completion is the ACTIVE set, not the live-worker set: pushed-pending
598
627
  // and pushed-green PRs still make the `runs-settled` release gate fail, so
599
- // a drain that cleared while one remained would admit work on top of a
600
- // batch the releases still see as unfinished (#776 review #2).
601
- if (d.store.activeRuns(d.project.name).length === 0) {
602
- cancelDrain(d.project.name);
603
- log("project drain completed: no active runs remain — drain cleared");
604
- } else {
605
- d.store.recordDispatch(d.project.name, summarizeHeldPass(settled));
606
- return;
628
+ // a window declared open over a remaining run would admit work on top of
629
+ // a batch the releases still see as unfinished (#776 review #2).
630
+ if (d.store.activeRuns(d.project.name).length === 0 && drain.drain.drainedAt === undefined) {
631
+ markDrained(d.project.name);
632
+ log(
633
+ `project drain: fleet drained — release window open until ${drain.drain.expiresAt}` +
634
+ " (claims stay paused; admission resumes at the deadline or on drain cancel)",
635
+ );
607
636
  }
637
+ d.store.recordDispatch(d.project.name, summarizeHeldPass(settled));
638
+ return;
608
639
  } else if (drain.kind === "error") {
609
640
  log(`project drain record invalid (${drain.problem}) — removed; this pass admits nothing`);
610
641
  d.store.recordDispatch(d.project.name, summarizeHeldPass(settled));
@@ -23,6 +23,7 @@ import { availabilityState, type AvailabilityState } from "../availability.ts";
23
23
  import { configPath, findProject, loadConfig, resolveCaps, resolveReleaseGrants, resolveReview, stateDir } from "../config.ts";
24
24
  import { digestScheduleState, type DigestScheduleState } from "../digest-schedule.ts";
25
25
  import { SPEND_SAMPLE_ROWS, SPEND_SAMPLE_RUNS } from "../doctor.ts";
26
+ import { EFFECTIVE_BUDGET_SAMPLE_RUNS, observeTurnBudget, type ObservedTurnBudget } from "../failure-class.ts";
26
27
  import type { CodeGraphHealth } from "../graph-health.ts";
27
28
  import { isPaused, pauseProvenance, setPaused } from "../pause.ts";
28
29
  import { branchName, route } from "../routing.ts";
@@ -189,6 +190,11 @@ export interface StatusSnapshot {
189
190
  */
190
191
  reporting?: ReportingSummary;
191
192
  caps: Caps;
193
+ /** #1063: the turn count the wall-clock ceiling actually buys at this
194
+ * project's observed per-turn latency, with the sample it was derived
195
+ * from. Absent when no qualifying completed run exists — never derived
196
+ * from the configured constants, whose ratio is a constant. */
197
+ workerTurnBudgetObserved?: ObservedTurnBudget;
192
198
  /**
193
199
  * The effective per-shape release grants. On the snapshot rather than re-read
194
200
  * by each renderer because #122 began with a grant nobody had looked at in
@@ -552,6 +558,17 @@ export function statusSnapshotFromStore(
552
558
  const observed = store.installSurfaces();
553
559
  return observed === undefined ? {} : { installSurfaces: observed };
554
560
  })(),
561
+ // #1063, the same read-not-probe discipline as the spend judgement above:
562
+ // derived from completed-run rows on this read, never probed at render
563
+ // time. The renderer decides whether the figure is materially lower.
564
+ ...(() => {
565
+ const budget = observeTurnBudget(
566
+ store.recentLatencySamples(p.name, EFFECTIVE_BUDGET_SAMPLE_RUNS),
567
+ caps,
568
+ p.workerModel,
569
+ );
570
+ return budget === undefined ? {} : { workerTurnBudgetObserved: budget };
571
+ })(),
555
572
  ...(dispatch === undefined ? {} : { dispatch }),
556
573
  ...(planUsage === undefined ? {} : { planUsage }),
557
574
  // Written by the tracker's hooks rather than polled, so the renderer does
package/src/daemon.ts CHANGED
@@ -34,7 +34,7 @@ export type {
34
34
  export { completionLastError, exhaustedSessionReason, verbDeps } from "./daemon/deps.ts";
35
35
 
36
36
  export type { CreateDrainOptions, DrainProblem, DrainRecord, DrainVerdict } from "./daemon/drain.ts";
37
- export { cancelDrain, consumeDrain, createDrain, drainPath, readDrain } from "./daemon/drain.ts";
37
+ export { cancelDrain, consumeDrain, createDrain, drainPath, markDrained, readDrain } from "./daemon/drain.ts";
38
38
 
39
39
  export type { AdmissionAckRecord } from "./daemon/ack.ts";
40
40
  export { admissionAckPath, daemonGeneration, readAdmissionAck, wakeDaemon, writeAdmissionAck } from "./daemon/ack.ts";
@@ -52,6 +52,7 @@ export {
52
52
  wakeOrchestratorForBlockedRun,
53
53
  wakeOrchestratorForMetConditions,
54
54
  watchOrchestrator,
55
+ watchWorkerProgress,
55
56
  } from "./daemon/supervision.ts";
56
57
 
57
58
  export { reconcilePanes } from "./daemon/panes.ts";
package/src/decisions.ts CHANGED
@@ -45,26 +45,35 @@ const NPM_SPEC = /^(@[^/@\s]+\/)?[^@\s]+@[^\s]+$/;
45
45
  const GREEN_CHECK_STATES: Record<string, true> = { success: true, neutral: true };
46
46
 
47
47
  /**
48
- * The run states a review revision may start from (#795) — the single
48
+ * The run states a review revision may start from (#795, #1101) — the single
49
49
  * definition shared by `conductor_pr_review` and the `pr-review-ready` watch,
50
50
  * so the verb's gate and the condition can never name different sets.
51
51
  *
52
52
  * A revision round resumes the exact run whose row owns the PR, so the
53
- * revisable states are exactly the terminal runs that pushed one: a settled
54
- * `pushed-green` row, or a `failed` / `killed` row — a run that capped or
55
- * failed *after* pushing a green PR. The PR is the durable artefact, the
56
- * exact-head green verification is the gate on "green at the reviewed SHA",
57
- * and a terminal row proves no worker is in flight, so findings are returned
53
+ * revisable states are exactly the terminal runs that own the named PR: a
54
+ * settled `pushed-green` row, a `failed` / `killed` row — a run that capped or
55
+ * failed *after* pushing a green PR and, since #1101, a `stopped` row that
56
+ * pushed one. Ownership is not carried by the state alone: selection runs
57
+ * through `runsForProjectPr`, so only a row that itself recorded the reviewed
58
+ * PR can ever reach this gate. The PR is the durable artefact, the exact-head
59
+ * green verification is the gate on "green at the reviewed SHA", and a
60
+ * terminal row proves no worker is in flight, so findings are returned
58
61
  * without the close-PR → unblock → continuation dance.
59
62
  *
63
+ * A `stopped` row has no settle sweep keeping it honest (nothing transitions a
64
+ * stopped row when its PR merges or closes), so like `failed` / `killed` its
65
+ * rounds re-read PR-open and green-at-head decisively before the claim — the
66
+ * dispatch pass refuses to wake a worker against a dead or moved PR.
67
+ *
60
68
  * Closed on purpose: a live row (`running` / `claimed`) is already doing its
61
69
  * own work, a `pushed-pending` PR is not green yet, and a `blocked` /
62
- * `orphaned` / `stopped` / `merged` row is not work returned for revision.
70
+ * `orphaned` / `merged` row is not work returned for revision.
63
71
  */
64
72
  export const REVISABLE_RUN_STATES: Record<string, true> = {
65
73
  "pushed-green": true,
66
74
  failed: true,
67
75
  killed: true,
76
+ stopped: true,
68
77
  };
69
78
 
70
79
  /**
@@ -92,8 +101,10 @@ export const PR_LOOKUP_WINDOW_MS = 30 * 24 * 60 * 60_000;
92
101
  * `running`, and a watch keyed only to checks woke the orchestrator before
93
102
  * `conductor_pr_review` was actionable), `pushed-pending` checks are still
94
103
  * settling, `blocked` may resume, `orphaned` is reconciled back to live at
95
- * startup, and `merged` means the PR lifecycle is over. When every row is
96
- * `stopped`, the newest one answers and the predicate fails closed.
104
+ * startup, and `merged` means the PR lifecycle is over. When every row of the
105
+ * history is `stopped`, the newest one answers and since #1101 it answers
106
+ * `ready` when it owns the named PR: stopping a worker that had already pushed
107
+ * must leave the PR reviewable rather than stranded.
97
108
  *
98
109
  * `no-owner` and `not-revisable` both fail closed: a review can never act, so
99
110
  * a watch must not wake, even when the checks are green.
package/src/diff-flags.ts CHANGED
@@ -37,7 +37,7 @@
37
37
  * repository.
38
38
  */
39
39
 
40
- import type { FileLane, PrDiff, PrDiffFile, SettlementFlag } from "./types.ts";
40
+ import type { FileLane, PrDiff, PrDiffFile, SettlementFlag, SettlementFlagKind } from "./types.ts";
41
41
 
42
42
  // ------------------------------------------------------------------ diff parse
43
43
 
@@ -657,21 +657,24 @@ export const UNREADABLE_TREE_FLAG: SettlementFlag = {
657
657
 
658
658
  /**
659
659
  * Whether a diff path counts as inside the declared lane: an explicitly
660
- * declared path, or the co-located test of one `foo.ts` vouches for
661
- * `foo.test.ts`, which is the "obviously intended" case. Other test shapes
662
- * (`.spec.ts`, pytest's `test_` prefix) are not vouched for: the rule is the
663
- * shape the fleet actually uses, and a lane that wants a differently-shaped
664
- * sibling declares it. Deliberately one-directional: a lane that declares a
665
- * *test* file does not vouch for its source, because declaring the test alone
666
- * is a narrower promise and widening it silently is exactly what this flag
667
- * exists to name. A containing directory never vouches for its contents
668
- * either the lane grammar names files, and a lane that means "everything
669
- * under `src/`" fails open exactly as an undeclared one would if it cannot
670
- * name them.
660
+ * declared path, the co-located test of one, or a descendant of a declared
661
+ * directory (#1091) admission's grammar accepts a trailing-`/` entry
662
+ * ("everything under `src/`"), so settlement honours the same contract rather
663
+ * than narrowing it back to exact files. The co-located allowance is the
664
+ * "obviously intended" case: `foo.ts` vouches for `foo.test.ts`. Other test
665
+ * shapes (`.spec.ts`, pytest's `test_` prefix) are not vouched for: the rule
666
+ * is the shape the fleet actually uses, and a lane that wants a
667
+ * differently-shaped sibling declares it. Deliberately one-directional: a lane
668
+ * that declares a *test* file does not vouch for its source, because declaring
669
+ * the test alone is a narrower promise and widening it silently is exactly
670
+ * what this flag exists to name. The directory rule keeps its trailing-slash
671
+ * boundary explicit: the entry already carries the separator, so `foo/`
672
+ * covers `foo/bar.ts` at any depth and can never vouch for `foobar/x.ts`.
671
673
  */
672
674
  function withinLane(path: string, declared: readonly string[]): boolean {
673
675
  if (declared.includes(path)) return true;
674
676
  for (const d of declared) {
677
+ if (d.endsWith("/") && path.startsWith(d)) return true;
675
678
  if (coLocatedTest(d) === path) return true;
676
679
  }
677
680
  return false;
@@ -693,11 +696,22 @@ function coLocatedTest(declared: string): string | undefined {
693
696
  * The lane is the effective declaration admission resolved at dispatch —
694
697
  * `effectiveLane(body, comments)`, so a pre-dispatch comment beats an older
695
698
  * body declaration — and the flag uses that resolved snapshot, never a re-parse
696
- * of the body. The finding names every delivered file outside it, which is the
699
+ * of the body. Each finding names the delivered files outside it, which is the
697
700
  * part a reviewer is worst placed to notice: the diff's own file list is the
698
701
  * only surface that shows the escape, and reading PR file lists by hand is
699
702
  * exactly what nothing else in the loop does.
700
703
  *
704
+ * An escape splits into up to two findings because it is up to two different
705
+ * events (#1096): an undeclared production module is scope escaping into code,
706
+ * while an undeclared test file is usually coverage arriving beside the
707
+ * behaviour it pins — the #1064/#1080 shape, required work the declaration
708
+ * could not have named. Naming the class on each finding keeps the second
709
+ * readable as what it usually is without letting it disguise the first. The
710
+ * declared source's own `.test` twin is admitted by {@link withinLane}, so
711
+ * the obvious case raises nothing — and the rule stays one-directional, so a
712
+ * lane declaring only the test still names its source when the worker edits
713
+ * it.
714
+ *
701
715
  * Fail-open, like admission: an issue with no lane declaration has nothing to
702
716
  * escape, so no flag — a flag on every undeclared run would be noise within a
703
717
  * day, worse than no flag. Advisory like every other flag here: a widened lane
@@ -710,11 +724,23 @@ function detectLaneEscape(audit: SettlementAudit, flags: SettlementFlag[]): void
710
724
  .map((f) => f.path)
711
725
  .filter((path) => !withinLane(path, lane.files));
712
726
  if (outside.length === 0) return;
713
- flags.push({
714
- kind: "lane-escape",
715
- file: "(lane)",
716
- detail: `PR diff touches files outside the declared file lane: ${outside.join(", ")}`,
717
- });
727
+ // Production first: the rarer, heavier event leads the report block.
728
+ const production = outside.filter((path) => !isTestPath(path));
729
+ const otherTests = outside.filter((path) => isTestPath(path));
730
+ if (production.length > 0) {
731
+ flags.push({
732
+ kind: "lane-escape",
733
+ file: "(lane)",
734
+ detail: `PR diff touches production files outside the declared file lane: ${production.join(", ")}`,
735
+ });
736
+ }
737
+ if (otherTests.length > 0) {
738
+ flags.push({
739
+ kind: "lane-escape",
740
+ file: "(lane)",
741
+ detail: `PR diff touches unrelated test files outside the declared file lane: ${otherTests.join(", ")}`,
742
+ });
743
+ }
718
744
  }
719
745
 
720
746
  function detectWeakening(audit: SettlementAudit, flags: SettlementFlag[]): void {
@@ -872,7 +898,7 @@ function scanHunks(
872
898
  * an extractor that reads any backtick span in the narrative as a claim would
873
899
  * flag every run within a day.
874
900
  *
875
- * Three tolerances make the matcher honest rather than decorative:
901
+ * Four tolerances make the matcher honest rather than decorative:
876
902
  *
877
903
  * - Claims are extracted only from a verified section — a `## Verified`-style
878
904
  * heading or an inline `Verified:` label — never from the narrative. This is
@@ -889,6 +915,14 @@ function scanHunks(
889
915
  * the worker *tried* and the guard stopped it (the honest #566/#570 shape,
890
916
  * whose own words are "CI owns them"), which is not the same finding as a
891
917
  * claim with no attempt at all.
918
+ * - Claims are positions before they are texts (#1102): a unit of the region
919
+ * claims what it opens with — a bullet or bare line whose lead is the
920
+ * backticked command, or a `**Verified:**` label declaring its whole row —
921
+ * never a span quoted mid-sentence. The soft-wrapped prose that quoted
922
+ * `issue is closed` inside #1097's Verified bullet is commentary whatever
923
+ * words it quotes, and the structural rule says so without a runner
924
+ * vocabulary that would silently stop auditing the first PR proved by
925
+ * anything else.
892
926
  */
893
927
 
894
928
  /** The verified-section markers a claim must live under: a heading whose text
@@ -949,20 +983,96 @@ function claimedCommand(span: string): boolean {
949
983
  return EXECUTABLE_TOKEN.test(words[0] ?? "");
950
984
  }
951
985
 
986
+ /** A markdown list marker opening a line: `- item`, `* item`, `1. item`,
987
+ * `2) item`. The required trailing whitespace is what keeps a bold
988
+ * `**Verified:**` row or an italic `*span*` from reading as a bullet. */
989
+ const LIST_MARKER = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]+/;
990
+
991
+ /** The inline verified label a claims row can open with — `**Verified:**`,
992
+ * `Verification:`, `proof.` — mirroring the label branch of
993
+ * {@link VERIFIED_MARKER}, so the very row that opens the region also parses
994
+ * as claims-bearing. */
995
+ const CLAIMS_ROW_LABEL = /^\s*(?:\*\*)?(?:Verif(?:ied|ication)|Proof)(?:\*\*)?\s*[:.]\s*/i;
996
+
997
+ /** Every backticked span in `text`, trimmed. */
998
+ function backtickSpans(text: string): string[] {
999
+ return [...text.matchAll(/`([^`\n]+)`/g)].map((match) => (match[1] ?? "").trim());
1000
+ }
1001
+
1002
+ /** The one backticked span `text` opens with, when it opens with one. */
1003
+ function leadingBacktickSpan(text: string): string | undefined {
1004
+ const match = /^`([^`\n]+)`/.exec(text);
1005
+ const span = (match?.[1] ?? "").trim();
1006
+ return span === "" ? undefined : span;
1007
+ }
1008
+
1009
+ /**
1010
+ * The logical line-units of the verified region. A heading or a list marker
1011
+ * always opens a unit, a blank line ends one, and every other non-blank line
1012
+ * is the markdown lazy continuation of the unit above it. That continuation
1013
+ * rule is the foundation of the #1102 fix: a bullet soft-wrapped across
1014
+ * physical lines stays ONE unit, so its wrapped second line — exactly where
1015
+ * #1097's quoted log line sat — can never pose as a fresh claim-bearing line.
1016
+ * The cost is deliberate and stated: bare claim lines stacked without markers
1017
+ * or blank lines merge into one unit, and only their first extracts. This
1018
+ * fleet writes claims as bullets, so the lost shape is the rarer one.
1019
+ */
1020
+ function* claimUnits(region: string): Generator<string[]> {
1021
+ let unit: string[] = [];
1022
+ for (const line of region.split("\n")) {
1023
+ if (/^#{1,6}\s/.test(line) || LIST_MARKER.test(line)) {
1024
+ if (unit.length > 0) yield unit;
1025
+ unit = [line];
1026
+ } else if (line.trim() === "") {
1027
+ if (unit.length > 0) yield unit;
1028
+ unit = [];
1029
+ } else {
1030
+ unit.push(line);
1031
+ }
1032
+ }
1033
+ if (unit.length > 0) yield unit;
1034
+ }
1035
+
1036
+ /**
1037
+ * The claim candidates one unit makes — a matter of position, not vocabulary
1038
+ * (#1102). A unit claims what it OPENS with:
1039
+ *
1040
+ * - a verified-label row (`**Verified:** …`, the #749/#731 semicolon-joined
1041
+ * shape) declares every span it carries a claim — the label is what marks
1042
+ * the whole row as proof;
1043
+ * - any other unit contributes only its leading span — `- \`cd omp && bun test …\`
1044
+ * — 42 pass, 0 fail.` or a bare `\`./scripts/deploy.sh\` — deployed.` line —
1045
+ * whose tail is commentary about the result.
1046
+ *
1047
+ * A span embedded after prose is neither: `…assert exactly one log line naming
1048
+ * \`issue is closed\`` quotes the transcript's own output, and quotation is
1049
+ * commentary whatever the quoted words spell.
1050
+ */
1051
+ function unitClaimSpans(unit: string[]): string[] {
1052
+ const lead = (unit[0] ?? "").replace(LIST_MARKER, "");
1053
+ if (CLAIMS_ROW_LABEL.test(lead)) {
1054
+ return backtickSpans([lead.replace(CLAIMS_ROW_LABEL, ""), ...unit.slice(1)].join("\n"));
1055
+ }
1056
+ const span = leadingBacktickSpan(lead);
1057
+ return span === undefined ? [] : [span];
1058
+ }
1059
+
952
1060
  /**
953
1061
  * The commands a PR body claims as proof, each deduplicated by its exact span.
954
- * Only spans inside the verified region count, and only command-shaped ones
955
- * the two filters are what keep `## Verified`-island prose out of the audit.
1062
+ * Extraction is structural twice over: only spans inside the verified region
1063
+ * count ({@link claimRegion}), and only spans in a claim-bearing position
1064
+ * within it ({@link unitClaimSpans}) — the two filters together are what keep
1065
+ * `## Verified`-island prose out of the audit.
956
1066
  */
957
1067
  function claimedProofCommands(body: string): string[] {
958
- const region = claimRegion(body);
959
1068
  const claims: string[] = [];
960
1069
  const seen = new Set<string>();
961
- for (const match of region.matchAll(/`([^`\n]+)`/g)) {
962
- const span = (match[1] ?? "").trim();
963
- if (span === "" || seen.has(span) || !claimedCommand(span)) continue;
964
- seen.add(span);
965
- claims.push(span);
1070
+ for (const unit of claimUnits(claimRegion(body))) {
1071
+ for (const span of unitClaimSpans(unit)) {
1072
+ if (seen.has(span) || !claimedCommand(span)) continue;
1073
+ seen.add(span);
1074
+ claims.push(span);
1075
+ }
966
1076
  }
967
1077
  return claims;
968
1078
  }
@@ -1229,6 +1339,21 @@ const RENDERED_FLAGS = 20;
1229
1339
 
1230
1340
  const HEADING = "settlement audit";
1231
1341
 
1342
+ /**
1343
+ * The kinds that mean coverage got weaker — the quiet signals the audit
1344
+ * exists to deliver, and the family mining.ts mines. Presentation keeps them
1345
+ * ahead of everything else and lane escapes last (#1096): a check that fires
1346
+ * on half of all merges gets skimmed, and a skimmed finding must not sit
1347
+ * between the reviewer and these. Kept in step with `WEAKENING_KINDS` in
1348
+ * mining.ts, which is the same four kinds spelled for mining.
1349
+ */
1350
+ const TEST_INTEGRITY_KINDS: Partial<Record<SettlementFlagKind, true>> = {
1351
+ "test-file-deleted": true,
1352
+ "test-disabled": true,
1353
+ "assertions-removed": true,
1354
+ "test-timeout-raised": true,
1355
+ };
1356
+
1232
1357
  /**
1233
1358
  * The flag block appended to a settlement report, or no lines at all.
1234
1359
  *
@@ -1253,7 +1378,17 @@ export function formatSettlementFlags(
1253
1378
  `${HEADING}: ${flags.length} advisory flag(s) — the run's state is unchanged by them` +
1254
1379
  (diff.truncated ? ", and the PR diff was too large to read in full" : ""),
1255
1380
  ];
1256
- for (const flag of flags.slice(0, RENDERED_FLAGS)) {
1381
+ // Presentation order is structural, not incidental: test-integrity flags
1382
+ // first, then everything else, lane escapes last (#1096). The analyser
1383
+ // emits them in roughly this order already, but this surface also renders
1384
+ // stored rows and post-settlement appends, whose order nobody chose.
1385
+ const rank = (flag: SettlementFlag): number =>
1386
+ TEST_INTEGRITY_KINDS[flag.kind] === true ? 0 : flag.kind === "lane-escape" ? 2 : 1;
1387
+ const ordered = flags
1388
+ .map((flag, index) => ({ flag, index }))
1389
+ .sort((a, b) => rank(a.flag) - rank(b.flag) || a.index - b.index)
1390
+ .map((entry) => entry.flag);
1391
+ for (const flag of ordered.slice(0, RENDERED_FLAGS)) {
1257
1392
  if (flag.kind === "pr-adopted") {
1258
1393
  lines.push(` ${flag.kind} — ${flag.detail}`);
1259
1394
  continue;
@@ -1270,10 +1405,30 @@ export function formatSettlementFlags(
1270
1405
  }
1271
1406
 
1272
1407
  /** The one-line form for `omp-conductor status`, where a flagged run has to be
1273
- * visible long after its escalation was delivered and deduplicated. */
1408
+ * visible long after its escalation was delivered and deduplicated. Lane
1409
+ * escapes are presented apart from the test-integrity family (#1096), so the
1410
+ * noisy check cannot bury the quiet one in one comma join. */
1274
1411
  export function settlementFlagSummary(flags: readonly SettlementFlag[] | undefined): string | undefined {
1275
1412
  if (flags === undefined || flags.length === 0) return undefined;
1276
- const kinds = [...new Set(flags.map((f) => f.kind))].join(", ");
1413
+ const integrity = new Set<SettlementFlagKind>();
1414
+ const lane = new Set<SettlementFlagKind>();
1415
+ const rest = new Set<SettlementFlagKind>();
1416
+ for (const { kind } of flags) {
1417
+ if (TEST_INTEGRITY_KINDS[kind] === true) integrity.add(kind);
1418
+ else if (kind === "lane-escape") lane.add(kind);
1419
+ else rest.add(kind);
1420
+ }
1421
+ const segments: { label?: string; kinds: SettlementFlagKind[] }[] = [];
1422
+ if (integrity.size > 0) segments.push({ label: "test-integrity", kinds: [...integrity] });
1423
+ if (rest.size > 0) segments.push({ kinds: [...rest] });
1424
+ if (lane.size > 0) segments.push({ label: "lane", kinds: [...lane] });
1425
+ // One family alone reads exactly as before — a label with nothing to
1426
+ // separate is furniture.
1427
+ const parts = segments.map((segment) =>
1428
+ segments.length > 1 && segment.label !== undefined
1429
+ ? `${segment.label}: ${segment.kinds.join(", ")}`
1430
+ : segment.kinds.join(", "),
1431
+ );
1277
1432
  const loud = flags.some((f) => f.unattributed === true) ? ", some unattributed" : "";
1278
- return `${HEADING}: ${flags.length} flag(s) — ${kinds}${loud}`;
1433
+ return `${HEADING}: ${flags.length} flag(s) — ${parts.join("; ")}${loud}`;
1279
1434
  }