/* * ⛔⛆ THE THING MERGED MUST BE THE THING GATED — AND NOTHING COMPARED THEM. * * `⟨q-6a4f0c38⟩`, from a real breach on kit#268. The sequence, which no single * seat could see alone: * * 10:23:14Z QA posts PASS bound to 6051193 * 10:23:22Z the merge runs <- 8 seconds later * but the branch head was 1d6deab by then: an author force-pushed * 10:28:06Z QA re-issues PASS bound to 1d6deab, AFTER the fact * * Three controls had to fail together: the force-push created the opportunity, * the merge did not re-read the head, and the recording step did not compare. * This module is the predicate for the two that are mechanisable. * * ⭐ WHY THE CHECK MUST BE TEMPORAL, and this is the trap that makes the naive * version certify the very breach it was built for: by the time anyone looks, * a PASS bound to `1d6deab` EXISTS. "Is there a PASS for the merged head?" is * TRUE for #268 today. Only "was there one AT OR BEFORE the merge" is false. * A re-issued verdict is an honest record that the CONTENT was verified; it is * not evidence that the MERGE was gated, and conflating them erases the event. * * ⭐ WHY THE SHA COMPARISON IS PREFIX-TOLERANT RATHER THAN EQUALITY. Measured * across all 45 verdict records on this fleet's room log: * * headRefOid length: 7 chars -> 14 records, 40 chars -> 31 records * * Verdicts are written by hand and abbreviate. An exact compare against `gh`'s * 40-char head would report "never gated" on ~31% of verdicts that DID gate — * false positives on correct merges, which is the failure that gets a check * switched off. MIN_SHA guards the other direction: a 4-character "sha" is not * an identifier, it is a collision, so it is REFUSED rather than matched. * * ⛔ AND "COULD NOT CHECK" IS NEVER "CHECKED AND CLEAN" — the rule this repo * already applies in `versionDrift`. No verdict found, an unreadable log and an * unreachable `gh` are each a REFUSAL with a reason, never a pass. */ /** Below this, an abbreviation is a collision rather than an identifier. */ export const MIN_SHA = 7; /** * Do two object names refer to the same commit, allowing either to be * abbreviated? Case-insensitive; hex only; both must reach MIN_SHA. */ export function shaAgrees(a: string | undefined, b: string | undefined): boolean { const x = (a ?? "").trim().toLowerCase(); const y = (b ?? "").trim().toLowerCase(); if (!/^[0-9a-f]+$/.test(x) || !/^[0-9a-f]+$/.test(y)) return false; if (x.length < MIN_SHA || y.length < MIN_SHA) return false; return x.length <= y.length ? y.startsWith(x) : x.startsWith(y); } /* * ⟨q-dcbaf544⟩ — A VERDICT NAMES ITS SENDER AND NEVER ITS GATER. The record's * `from` is whoever SENT it; when a gate is routed to a seat whose role cannot * emit `verdict` (repo-owner, console — David's ruling: authority does not * move), a gate-runner SCRIBES it, and the gater then survives only in prose. * A name in prose is a mention; a name in a field is a position — only the * position survives a scanner. So the record carries `gatedBy` (who judged) * and `scribe` (who transcribed), the spelling the three live scribed records * already used, and every reader attributes by `gatedBy ?? from`. */ export type PassVerdict = { head: string; ts: number; from: string; result: string; channel?: "bus" | "pr"; gatedBy?: string; scribe?: string }; /** Who JUDGED: the typed gater when scribed, else the sender. Never `from` alone. */ export const gaterOf = (v: Pick): string => v.gatedBy ?? v.from; const nonEmpty = (x: unknown): string | undefined => (typeof x === "string" && x.trim() ? x : undefined); /** The PR number a verdict record is about, from its `cites`. */ function citedPr(cites: unknown): string | null { if (!Array.isArray(cites)) return null; for (const c of cites) { const ref = String((c as { ref?: unknown })?.ref ?? ""); const m = ref.match(/#(\d+)/); if (m) return m[1]; } return null; } /** * Every verdict record in a room log that is about PR `pr`. * * Tolerant of unparseable lines by SKIPPING them and reporting how many, rather * than throwing: a log this cannot fully read still answers the question for the * lines it can, and the caller is told the denominator it actually saw. */ export function verdictsFor(logText: string, pr: string): { verdicts: PassVerdict[]; lines: number; unparsed: number } { const lines = logText.split("\n").filter((l) => l.trim()); let unparsed = 0; const verdicts: PassVerdict[] = []; for (const l of lines) { let o: Record; try { o = JSON.parse(l) as Record; } catch { unparsed++; continue; } const r = o.record as { type?: string; payload?: Record; cites?: unknown } | undefined; if (!r || r.type !== "verdict") continue; if (citedPr(r.cites) !== pr) continue; const gatedBy = nonEmpty(r.payload?.gatedBy); const scribe = nonEmpty(r.payload?.scribe); verdicts.push({ head: String(r.payload?.headRefOid ?? ""), ts: Number(o.ts ?? 0), from: String(o.from ?? ""), result: String(r.payload?.result ?? ""), ...(gatedBy ? { gatedBy } : {}), ...(scribe ? { scribe } : {}), }); } return { verdicts, lines: lines.length, unparsed }; } /* * ⟨q-5a93c2d7⟩ — GATEDNESS IS A PROPERTY OF (HEAD SHA, TYPED VERDICT), NOT OF * CLOCK ORDERING. Measured on the two UNGATED records `land` had written: * * #309 qa's PASS comment on the PR 16:10:12Z · merged 16:10:16Z · bus record 16:10:19Z * #310 PR comment 16:20:14Z · merged 16:20:18Z · bus record 16:20:21Z * * The PR carried a clean typed PASS four seconds BEFORE each merge; the bus * record trailed the merge by three; `gatedAt` read only the bus and only * records with ts ≤ mergedAt, and called both merges UNGATED. With one true * (#299) and two false UNGATEDs on the record, the marker had stopped * discriminating — the one-label-two-states failure, on the artefact that * exists to police merges. * * So: a verdict NAMING THE MERGED HEAD is a verdict ABOUT that head whenever * it was written and wherever it was recorded — the bus (any room) or the PR * page (the typed line, #313's grammar; #299's "not a verdict" review does * not match it). `recordedAt > mergedAt` imports a precondition the question * never had. THE COST IS ACCEPTED AND DISCLOSED: #268's own records now read * gated — qa re-issued a PASS bound to the merged head 4m44s after the merge * — and the report says so IN WORDS beside "gated", because a reader who sees * "gated" without "recorded 4m44s after the merge" has been told half a fact. */ /* * ⟨q-6f4a19c8⟩ — THE EARLIEST MATCH IS USUALLY THE PR COMMENT, AND THE PR * CHANNEL CANNOT NAME A GATER: one GitHub account serves six seats, so a * citation built from it reads "by davidbalzan" for a merge QA gated. Measured * on qa's own land dry run at #316: #310 and #316, both gated by qa, neither * citation said so. The earliest-match rule is CORRECT (it is what stops a late * bus record shadowing a PR comment that preceded the merge) and stays; the * remedy sits beside it — the earliest TIME and the identifiable AUTHOR need * not be the same record. When the earliest match is a PR comment and a bus * verdict names the same head, the citation carries both: the SEAT from the * bus, first seen on the PR at the comment's time. A PR-only verdict still * names the account, and says in words that it is an account, not a seat. */ export type GateVerdict = | { gated: true; by: PassVerdict; lateByMs: number | null; verified: string; /** `gatedBy ?? from` of the record that can NAME a seat; the PR account only when no channel can. */ gater: string; attribution: "seat" | "account"; /** The bus record that named the seat when `by` is a PR comment. */ seatRecord?: PassVerdict; } | { gated: false; reason: string; crossed?: { gatedSha: string; at: number; channel: string }[] }; export const fmtLate = (ms: number): string => (ms < 60_000 ? `${Math.round(ms / 1000)}s` : ms < 3_600_000 ? `${Math.floor(ms / 60_000)}m${Math.round((ms % 60_000) / 1000)}s` : `${Math.floor(ms / 3_600_000)}h${Math.floor((ms % 3_600_000) / 60_000)}m`); /** * Was `head` gated by a typed PASS bound to it — in any channel, at any time? * * `mergedAt` is DISCLOSURE, not a filter: when known and the earliest matching * PASS postdates it, the answer is still gated and `verified` says by how long * the record trailed the merge. The EARLIEST matching PASS is the one cited, * so a PR comment that preceded the merge is not shadowed by a bus record that * followed it (#309's shape). */ export function gatedBy(verdicts: PassVerdict[], head: string, mergedAt: number | null = null): GateVerdict { const passes = verdicts.filter((v) => v.result === "pass"); if (passes.length === 0) { return { gated: false, reason: `no typed PASS verdict exists for this PR in any channel — not checked, which is not the same as checked and passing` }; } const matches = passes.filter((v) => shaAgrees(v.head, head)).sort((a, b) => a.ts - b.ts); const by = matches[0]; if (!by) { return { gated: false, reason: `the head being judged (${head.slice(0, 7)}) matches no typed PASS in any channel — the thing merged is not the thing gated`, crossed: passes.map((v) => ({ gatedSha: v.head, at: v.ts, channel: v.channel ?? "bus" })), }; } const lateByMs = mergedAt !== null && Number.isFinite(mergedAt) ? Math.max(0, by.ts - mergedAt) : null; // ⟨q-6f4a19c8⟩ — the seat-naming record, when the earliest match cannot name one. const seatRecord = by.channel === "pr" ? matches.find((v) => v.channel !== "pr") : undefined; const gater = seatRecord ? gaterOf(seatRecord) : gaterOf(by); const attribution: "seat" | "account" = by.channel === "pr" && !seatRecord ? "account" : "seat"; const scribed = (v: PassVerdict) => (v.gatedBy && v.gatedBy !== v.from ? `, scribed by ${v.from}` : ""); const who = seatRecord ? `${gater} (bus${scribed(seatRecord)}), first seen on the PR at ${new Date(by.ts).toISOString()} as ${by.from}` : by.channel === "pr" ? `${by.from} (on the PR — a shared account, not a seat; no bus verdict names this head)` : `${gater} (on the bus${scribed(by)})`; const verified = lateByMs === null ? `verified at head ${head.slice(0, 7)} by ${who}` : lateByMs > 0 ? `verified at head ${head.slice(0, 7)} by ${who}; the verdict was RECORDED ${fmtLate(lateByMs)} AFTER the merge — the content was judged, the merge was not covered when it happened` : `verified at head ${head.slice(0, 7)} by ${who} before the merge`; return { gated: true, by, lateByMs, verified, gater, attribution, ...(seatRecord ? { seatRecord } : {}) }; } /** * ⛔ #313's GRAMMAR, KEPT ONLY FOR BACKWARDS COMPATIBILITY — IT MATCHED NOTHING IN * THE FIELD. `QA GATE — **PASS** @ \`sha\`` was ruled canonical and then had ZERO * ADOPTION: measured against the 9 merges of 2026-09-15, it matched 0 verdict * lines while all 9 merges carried one, so `unverdicted-merge` fired 9 times on 9 * correct merges — 100% false positive, which trains the room to ignore the one * instrument protecting the convention. * * ⭐ THE LESSON IS WHY THIS CONSTANT IS NO LONGER THE PREDICATE: A REGEX OVER PROSE * TESTS THE PHRASING, NOT THE CLAIM. The verdicts were never missing; only the * agreed wording was. Nobody noticed because every verdict is read BY EYE at gate * time and they all read fine — the instrument and the humans disagreed silently * for a day. * * ⚠ SCOPE, CORRECTED: "zero adoption" IS TRUE OF A CONSUMER FLEET AND FALSE OF THIS REPO. * Measured: 0 of 9 of that fleet's merges carry this grammar; 4 of 6 kit merges do, verbatim * (`#342`, `#340`, `#337`, `#336`). The original write-up scoped a measurement to one * consumer and stated it universally. * * ⛔⛔ AND DO NOT BELIEVE THIS CONSTANT IS WHAT KEEPS THAT KIT GRAMMAR WORKING — I * CLAIMED THAT AND IT IS FALSE. `verdictBoundTo` → `verdictClaimsIn` NEVER references * `VERDICT_COMMENT`; it survives only in the legacy `verdictShasIn`. Compatibility comes * from `DECLARES` accepting the `(?:QA )?GATE\b` opener. ⇒ THE TRAP THAT MAKES THIS * WORTH WRITING DOWN: deleting this constant as dead code leaves the kit grammar working, * so the experiment CONFIRMS the false mechanism — whoever ran it would conclude they had * removed something load-bearing and got away with it. If you are removing it, the thing * to check is `DECLARES`, not this. * * ⛔ ⟨q-3cd5a77d⟩ THE RESULT WORD MUST STAND IN THE CLAIM POSITION — right after the opener, past * only separators (`:` `—` `–` `-`, spaces) and an optional `**`. The first version accepted PASS or * FAIL ANYWHERE before the `@`, so a ROUTING header read as a verdict: * `GATE, prioritised: #359 (⟨q-3605ede3⟩, test-all prints PASS after FAILED) @ 0826519e…` * — the word was in the PR's TITLE. Bus history is permanent, so that one DM (11:19:43Z) turned * `verdict-names-gater`'s live-population test red on every chain on the machine, and made * `stall_check` report #359 as `reported-unscribed` with the COORDINATOR claiming PASS at its head. * Measured before the change, old vs new over 126 real lines (the 61-line verdict corpus + every * distinct line the old form matched on the live bus and in kit PR comments): they differ on * exactly that routing line, and agree — result word and sha — on the other 125. */ export const VERDICT_COMMENT = /^(?:QA GATE|\*\*GATE|GATE)\b[\s:—–-]*(?:\*\*\s*)?(PASS|FAIL)\b[^\n]*?@\s*`?([0-9a-f]{7,40})`?/; /** * WHAT MAKES A VERDICT BINDING, expressed as a shape instead of a phrase: a * DISPOSITION and a SHA, on ONE LINE, with the disposition ahead of the `@`. The * caller then decides bindingness by comparing that sha to the head it cares * about — the claim, not the header. * * Derived from the five spellings actually in the field on 2026-09-15, none of * which the constant above matches: * `## QA verdict — DONE: PASS \`#970\` @ \`sha\` · mode: full-local · base …` * `## QA verdict — DONE: CONTENT PASS \`#964\` @ \`sha\`` * `## QA verdict — DONE: READINESS RELEASED \`#964\` @ \`sha\`` * `## QA-2 — FULL PASS (content + readiness) @ \`sha\`` * `## qa-2 — CONTENT PASS (re-bind) @ \`sha\` · READINESS NOT CLEAR` * * ⛔ THE HALVES ARE DISTINGUISHED, BECAUSE A CONTENT PASS IS NOT A CLEARANCE. It * says so in its own body ("READINESS NOT ESTABLISHED — do not merge yet"), so * counting it as a whole verdict would license exactly the merge it forbids. A * caller wanting "was this clear to merge" needs a WHOLE verdict, or BOTH halves. * * ⛔ ORDER MATTERS IN THE CLASSIFIER: `CONTENT PASS` and `READINESS RELEASED` are * tested before bare `PASS`, and `READINESS NOT CLEAR`/`NOT ESTABLISHED` must not * read as a readiness half — the live `## qa-2 — CONTENT PASS (re-bind) @ sha · * READINESS NOT CLEAR` line carries both phrases and is a CONTENT half only. */ export type VerdictClaim = { result: string; sha: string; half: "whole" | "content" | "readiness" }; const NEGATED_READINESS = /READINESS\s+(?:NOT|IS\s+NOT)\b/i; /** * ⛔ THE LINE MUST DECLARE, NOT REMARK — and this anchor is here because removing it * BROKE THE AIDE'S #299 CONTROL, which caught it immediately. That control's sharpest * fixture is `Looks like a PASS to me — see the run @ \`sha\` for the log; not a gate.`: * uppercase PASS, an `@`, and the MERGED HEAD's own sha. Disposition-plus-bound-sha alone * counts it, and a chat remark then silences the check for a genuinely unverdicted merge. * * So a claim is a HEADING (every live verdict on 2026-09-15 is `## …`) or the legacy gate * opener. That is still structure rather than phrasing — what it requires is that someone * DECLARED a disposition, not that they used a particular sentence — and it is the part of * #313's grammar worth keeping: `QA GATE` was the wrong half to demand, `is a declaration` * was the right one. */ const DECLARES = /^\s*(?:#{1,6}\s+|\*\*(?:QA )?GATE|(?:QA )?GATE\b)/; /** An explicit disclaimer on the same line is dispositive — the author says it is not one. */ const DISCLAIMED = /\bnot a (?:gate|verdict)\b/i; /** * ⛔⛔ A WITHDRAWAL IS NOT THE VERDICT IT WITHDRAWS, AND THE NAIVE READING IS THE DANGEROUS ONE. * A consumer fleet's gate seat posted these on one of its PRs; both bound as a clean PASS before this line * existed: * `## ⛔ VOIDING my PASS-HOLD @ — that head is two moves dead` * `## ⛔ VOIDING my PASS @ ` * A line that revokes a verdict names that verdict — so matching the disposition word inside it * turns a RETRACTION into a CLEARANCE. That is a FALSE NEGATIVE in a watchdog, which is * indistinguishable from a clean run: `unverdicted-merge` would stay silent on a PR merged after * its verdict was voided, which is precisely the merge it exists to catch. * * ⛔⛔ AND POSITION IS THE DISCRIMINATOR, NOT PRESENCE — the first version tested this pattern * ANYWHERE on the line and failed in the OPPOSITE direction. A verdict that REPLACES an earlier * one routinely says so in its own header, which is the practice that keeps a superseded verdict * VISIBLE instead of silently edited: * `## QA GATE — **FAIL** @ — superseding my CONTENT PASS above` ← verbatim, a consumer fleet's PR * Discarding that one makes `unverdicted-merge` FIRE ON A PROPERLY GATED MERGE — and it * concentrates where gating was MOST active, because a PR whose verdict got corrected is one that * received MORE scrutiny. The instrument would go loudest exactly where it is least needed. * * ⇒ IN A WITHDRAWAL THE VERB PRECEDES THE DISPOSITION; IN A SUPERSEDING VERDICT THE DISPOSITION * COMES FIRST. qa-2's shape, and it is measured rather than reasoned: across ALL 124 verdict-shaped * lines in both repos (101 of them declaration-shaped), exactly TWO carry a revocation verb, and * they split correctly by position — 1 withdrawal, 1 superseding verdict, 99 plain verdicts. * * ⚠ THE RESIDUAL, STATED WITH ITS DENOMINATOR RATHER THAN LEFT IMPLIED: `PASS @ — * SUPERSEDED, see below` puts the verb after the disposition and would be ACCEPTED. qa-2 raised it * as its own invention and flagged it as not field-observed; the corpus agrees — ZERO of 124. It is * also partly self-correcting, since whatever supersedes it binds the same sha later. */ const REVOCATION_VERB = /\b(?:VOID(?:ING|ED)?|WITHDRAW(?:N|ING)?|RETRACT(?:ED|ING)?|SUPERSED(?:ED|ING|ES))\b/i; /** True only when the revocation verb comes BEFORE the disposition it names. */ function isWithdrawal(line: string, dispositionIndex: number): boolean { const verb = REVOCATION_VERB.exec(line); return verb !== null && verb.index < dispositionIndex; } /** * ⛔ `PASS-HOLD` IS A DOCUMENTED DO-NOT-MERGE VERDICT (`coord-qa/SKILL.md:71,74`) AND `\bPASS\b` * MATCHED INSIDE IT, because `-` is a non-word character. So the one verdict type whose entire * meaning is "do not merge" classified as a clearance and BOUND. * ⚠ It had not yet fired, and that was LUCK: qa-2's own `#974` PASS-HOLD missed only because the * head moved and the sha stopped agreeing. Nothing in the classifier prevented it. */ const HOLD = /\bPASS-HOLD\b/i; /** * ⭐ FOUND BY THE CORPUS, NOT BY A GATE — the fourth non-clearance form, and the first one this * suite caught itself rather than a reviewer catching it: * `## QA — **READINESS REFUSED** @ · the CONTENT PASS above still stands` (a consumer fleet's PR) * ⚠ IT ALREADY BEHAVED CORRECTLY, AND ONLY BY ACCIDENT: `READINESS REFUSED` is not a disposition, * and the `CONTENT PASS` it mentions sits AFTER the `@ `, so no claim matched. Reorder that * sentence — `the CONTENT PASS above still stands @ ` — and the refusal would have BOUND as a * content half. Naming it makes the behaviour a decision instead of a word-order coincidence. */ const REFUSED = /\bREADINESS\s+REFUSED\b/i; /** A disposition ahead of an `@ sha` on the same line. The sha's binding is the caller's test. */ const CLAIM_LINE = /\b(READINESS\s+RELEASED|CONTENT\s+PASS|FULL\s+PASS|PASS-HOLD|PASS|FAIL)\b[^\n]*?@\s*`?([0-9a-f]{7,40})`?/i; export function verdictClaimsIn(comments: { body: string }[]): VerdictClaim[] { const out: VerdictClaim[] = []; for (const c of comments) { for (const line of String(c.body ?? "").split("\n")) { // ⛔ A HOLD IS A NON-CLEARANCE THAT NAMES ITS OWN DISPOSITION, so it is rejected outright. if (!DECLARES.test(line) || DISCLAIMED.test(line) || HOLD.test(line) || REFUSED.test(line)) continue; const m = CLAIM_LINE.exec(line); if (!m) continue; // ⛔ A WITHDRAWAL IS REJECTED BY POSITION, NOT BY PRESENCE — see isWithdrawal. Testing the // verb anywhere on the line discards the superseding verdicts that carry it AFTER the // disposition, which fires the watchdog on properly gated merges. if (isWithdrawal(line, m.index)) continue; const word = m[1]!.toUpperCase().replace(/\s+/g, " "); const sha = m[2]!; if (word === "READINESS RELEASED") { out.push({ result: "PASS", sha, half: "readiness" }); } else if (word === "CONTENT PASS") { out.push({ result: "PASS", sha, half: "content" }); } else if (word === "FAIL") { out.push({ result: "FAIL", sha, half: "whole" }); } else { // bare PASS / FULL PASS — a whole verdict unless the same line withholds readiness out.push({ result: "PASS", sha, half: NEGATED_READINESS.test(line) ? "content" : "whole" }); } } } return out; } /** * Is there a verdict BOUND TO `head` — a whole one, or both halves? This is the * question `unverdicted-merge` actually asks, and it is the seam #964 exposed: * that PR published CONTENT PASS and READINESS RELEASED as two separate comments, * so a rule scoped to a whole verdict misses a merge that was fully gated. * * ⭐ A SUPERSEDED VERDICT CANNOT SATISFY THIS, BY CONSTRUCTION RATHER THAN BY A * SPECIAL CASE. #965's FAIL cited `ab58f9455` — an object that does not exist — * and was corrected in a later comment. Requiring agreement with the head that * MERGED means a sha naming any other tree (wrong, stale, or nonexistent) simply * does not bind. No "is this superseded" heuristic is needed and none is used. */ export function verdictBoundTo(comments: { body: string }[], head: string): { bound: boolean; why: string } { const claims = verdictClaimsIn(comments).filter((v) => shaAgrees(v.sha, head)); const whole = claims.find((v) => v.half === "whole"); if (whole) return { bound: true, why: `${whole.result} bound to ${head.slice(0, 7)}` }; const content = claims.some((v) => v.half === "content"); const readiness = claims.some((v) => v.half === "readiness"); if (content && readiness) return { bound: true, why: `CONTENT PASS + READINESS RELEASED bound to ${head.slice(0, 7)} (split halves)` }; if (content) return { bound: false, why: `only a CONTENT half at ${head.slice(0, 7)} — readiness never released, which is the half that clears a merge` }; if (readiness) return { bound: false, why: `only a READINESS half at ${head.slice(0, 7)} — no content verdict` }; return { bound: false, why: `no disposition bound to ${head.slice(0, 7)}` }; } export function verdictShasIn(comments: { body: string }[]): { result: string; sha: string }[] { const out: { result: string; sha: string }[] = []; for (const c of comments) { for (const line of String(c.body ?? "").split("\n")) { const m = VERDICT_COMMENT.exec(line); if (m) out.push({ result: m[1]!.toUpperCase(), sha: m[2]! }); } } return out; } /** PR comments as verdict records — the second channel. A comment with no typed line yields nothing. */ export function prVerdictsIn(comments: { body: string; createdAt?: string; author?: string }[]): PassVerdict[] { const out: PassVerdict[] = []; for (const c of comments) { for (const v of verdictShasIn([c])) { out.push({ head: v.sha, ts: Date.parse(String(c.createdAt ?? "")) || 0, from: String(c.author ?? "pr-comment"), result: v.result.toLowerCase(), channel: "pr" }); } } return out; }