diff --git a/site/pipeline.html b/site/pipeline.html
index cbff7be..445587c 100644
--- a/site/pipeline.html
+++ b/site/pipeline.html
@@ -162,7 +162,10 @@
.fsm-state.rising .fsm-spark .sd, .fsm-state.lead .fsm-spark .sd { fill: var(--crit); }
.fsm-rise { color: var(--crit); font-size: 12px; font-weight: 700; line-height: 1; }
.fsm-state .sc { font-size: 23px; font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
- .fsm-state .sk { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--ink-2); margin-top: 1px; }
+ /* A box's label comes off the snapshot too (a descriptor's `label`/`key`), so it wraps
+ inside its box instead of overflowing the grid. */
+ .fsm-state .sk { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--ink-2); margin-top: 1px;
+ overflow-wrap: anywhere; word-break: break-word; }
.fsm-state .sa { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-3); margin-top: 3px; }
.fsm-list { margin: 18px 0 4px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; display: none; }
.fsm-list.open { display: block; }
@@ -179,6 +182,17 @@
--crit (defined in both themes) plus wording that says which — the colour is never the
only carrier. Two classes deep so it outweighs `.fsm-list .empty`'s own colour. */
.fsm-list .empty.miswired { color: var(--crit); }
+ /* Conservation defect (rain-org-health#164): occupancy the snapshot carries that its own
+ descriptor list does not claim — inventory the machine renders NOWHERE, the exact shape
+ of the blocked-on drift that hid 17 PRs. Semantic --crit (defined in every theme scope)
+ plus wording that names what is unclaimed: the colour is never the only carrier. */
+ .fsm-defect { border: 1.5px solid var(--crit); border-radius: 11px; padding: 12px 14px;
+ margin: 12px 0 4px; color: var(--crit); font-size: 13px; line-height: 1.5; }
+ .fsm-defect .fd-h { font-weight: 650; }
+ /* The band quotes UNTRUSTED snapshot strings (state keys, lane names, counts keys), so
+ one long unbroken token must wrap rather than push the panel sideways. */
+ .fsm-defect .fd-li { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; margin-top: 4px;
+ overflow-wrap: anywhere; word-break: break-word; }
.fsm-note { color: var(--ink-3); font-size: 12px; margin: 8px 2px 0; line-height: 1.5; }
.fsm-note a, .fsm-note code { color: var(--ink-3); }
.fsm-lane { margin: 14px 0 2px; }
@@ -397,9 +411,14 @@
Pipeline state machine
// record of a machine that no longer exists.
//
// A fold maps a retired key onto the state that ABSORBED its inventory — so a retired key
- // with no absorbing successor has NO entry. `blockedInfra`'s exit was the run ending
- // (issue-pr-cron#108): no surviving inbox measures what it measured, summing it into any
- // live series would misstate that series' past, and its samples are drawn nowhere.
+ // with no absorbing successor has NO entry. `blockedDeploy` (issue-pr-cron#225) and
+ // `blockedInfra` (issue-pr-cron#108) have none — see RETIRED_NO_SUCCESSOR below: no
+ // surviving inbox measures what either measured, summing them into any live series would
+ // misstate that series' past, and their samples are drawn nowhere.
+ //
+ // FALLBACK-ONLY: a snapshot carrying `stateDescriptors` names each state's folds itself
+ // (`histFold`, rain-org-health#164) and the descriptor path passes that list to
+ // `seriesFor`, so this Map applies only when the frozen STATES table below renders.
const HIST_FOLD = new Map([
["unvetted", ["awaitingReVet"]],
["reject", ["humanReject", "relink"]],
@@ -407,10 +426,13 @@ Pipeline state machine
]);
// A state's inventory series from the rollup: one point per refresh that CARRIED this
// state's counts key, or any key folded into it (a snapshot carrying NONE of them ⇒ no
- // point, not a zero).
- const seriesFor = (histKey) => {
+ // point, not a zero). `fold` — when the caller passes one — is the descriptor's own
+ // `histFold` list and REPLACES the Map lookup outright: an empty list folds nothing,
+ // never falls back to the hand-maintained entries. Callers rendering the frozen
+ // fallback pass no fold and get the Map.
+ const seriesFor = (histKey, fold) => {
if (!histKey) return [];
- const keys = [histKey, ...(HIST_FOLD.get(histKey) || [])];
+ const keys = [histKey, ...(fold || HIST_FOLD.get(histKey) || [])];
const out = [];
for (const p of hist) {
const counts = p && p.counts;
@@ -568,14 +590,21 @@ Pipeline state machine
//
// The tool's `human-queue --json` `lanes` object is still keyed by LIFECYCLE phase
// (vet-lifecycle / vetter-verdicts / producer-blocked / human-decisions) and is SPARSE
- // (only non-empty states emitted). So each state descriptor carries `lane` = where its
+ // (only non-empty states emitted). So each state entry carries `lane` = where its
// { count, prs:[{repo,number,url,title}] } is READ from, independent of `owner` = how
- // it is GROUPED. STATES is the canonical shape — a state absent from the JSON renders
- // zero (dimmed), keeping the machine's whole shape visible. laneItems maps a state key
- // -> its PR list for the click-to-expand detail. (Ownership lives in this map until the
- // tool emits an `owner` per state as the single source of truth — an issue-pr-cron
- // follow-up, which would also split the two dual-owner states called out below.)
+ // it is GROUPED. laneItems maps a state key -> its item list for the click-to-expand
+ // detail.
+ //
+ // WHERE THE SHAPE COMES FROM (issue-pr-cron#130 / rain-org-health#164): a snapshot
+ // carrying `stateDescriptors` IS the machine — the tool emits each state's owner,
+ // label, act, kind, occupancy source and history folds, and the panel renders exactly
+ // that, in that order, so adding/renaming/re-laning/retiring a state propagates on the
+ // next refresh with no edit here. The hand-written STATES table below survives ONLY as
+ // the FROZEN fallback for descriptor-less snapshots (the same posture as the legacy
+ // no-`lanes` branch): on such a snapshot a state absent from the JSON renders zero
+ // (dimmed), keeping the machine's whole shape visible.
const hasLanes = hq.lanes && typeof hq.lanes === "object";
+ const descriptors = Array.isArray(hq.stateDescriptors) ? hq.stateDescriptors : null;
// EVERY rendered state registers its item list here, whichever branch built it — a lane
// cell's `prs` for a lane state, the named top-level array for a `fromCounts` state. The
// click-to-expand reads ONLY this map, so registration is the single wiring point instead of
@@ -594,7 +623,12 @@ Pipeline state machine
issue: !!issue,
count: Number(count) || 0,
});
- const laneState = (lane, state) => (hq.lanes[lane] && hq.lanes[lane][state]) || null;
+ // Total over ANY snapshot: the descriptor path reads lane occupancy even when `lanes`
+ // is absent or junk (sparse-absent ⇒ null ⇒ the box renders 0).
+ const laneState = (lane, state) => {
+ const l = hasLanes ? hq.lanes[lane] : null;
+ return (l && typeof l === "object" && l[state]) || null;
+ };
const stateBox = (key, label, count, act, kind, series) => {
const b = el("button", "fsm-state " + kind + (count ? "" : " zero"));
@@ -742,12 +776,15 @@ Pipeline state machine
lead.b.setAttribute("aria-label", lead.label + ": largest queue for this actor (start here)");
};
+ // FROZEN (rain-org-health#164): this table renders ONLY for a snapshot with no
+ // `stateDescriptors` — the same posture as the legacy no-`lanes` branch below. The live
+ // vocabulary is the tool's own descriptor list; a new, renamed, re-laned or retired
+ // state reaches the page as data, never as an edit here.
+ //
// Every modeled state → the actor whose inbox it sits in (`owner`), the human-queue
// `lane` its items live under, the next `act`, and a display `kind` (flow = moving,
// rule = needs a human ruling, blk = a stuck/rework pile). The dual-owner
- // resolutions (each defensible against the README FSM's own exit edges):
- // ai:blocked-deploy → HUMAN — FSM exit is "human resolves deploy → re-work"; the
- // producer only DIAGNOSES the failed deploy, the human funds/dispatches it.
+ // resolution (defensible against the README FSM's own exit edges):
// ai:blocked-on → VETTER — issue-pr-cron#161: deps are typed `--blocked-by` refs
// and the vetter's state-load clears the flag the run after every dep merges or
// closes, so the vetter's cron run is the mover and no human action sits on the
@@ -804,7 +841,7 @@ Pipeline state machine
// only picks the path tried first and github's own /issues/↔/pull/ redirect resolves the
// rest — see `urlOf`.
{ owner: "vetter", state: "closeCandidateUnvetted", fromCounts: "closeCandidateUnvetted", items: "closeCandidateUnvetted", issue: true, hist: "closeCandidateUnvetted", act: "vet the flag", kind: "flow", label: "ai:close-candidate (unvetted)" },
- // Vetter-owned per the dual-owner resolutions above (issue-pr-cron#161): the vetter's
+ // Vetter-owned per the dual-owner resolution above (issue-pr-cron#161): the vetter's
// state-load clears the flag once every typed dep merges/closes, so the act is that
// clearance, not a human merge. The tool keys this cell under `vet-lifecycle`;
// `staleLane` keeps a snapshot from before that re-key readable — see the lane read
@@ -825,7 +862,6 @@ Pipeline state machine
{ owner: "human", state: "leak", fromCounts: "leaks", items: "leaks", issue: false, hist: "leaks", act: "model it", kind: "blk", label: "not in any modeled state" },
{ owner: "human", state: "ai:ready", lane: "vetter-verdicts", hist: "ready", act: "merge", kind: "flow" },
{ owner: "human", state: "ai:design", lane: "vetter-verdicts", hist: "design", act: "rule on design Q", kind: "rule" },
- { owner: "human", state: "ai:blocked-deploy", lane: "producer-blocked", hist: "blockedDeploy", act: "resolve deploy", kind: "blk" },
{ owner: "human", state: "human:design", lane: "human-decisions", hist: "humanDesign", act: "decide", kind: "rule" },
// Close-candidate flags the vetter UPHELD (issue-pr-cron#73/#212): the flag survived
// judgement, so a human disposes of the subject. ONE inbox for issues and PRs alike
@@ -844,10 +880,15 @@ Pipeline state machine
{ owner: "human", state: "closeCandidateUpheld", fromCounts: "closeCandidateUpheld", items: "closeCandidateUpheld", issue: true, hist: "closeCandidateUpheld", act: "close", kind: "rule", label: "ai:close-candidate (upheld)" },
];
- // Retired states whose FSM exit was "the run ends" — NOT a transition into a surviving
- // state. `ai:blocked-infra` (issue-pr-cron#108): infrastructure being down is a property
- // of the moment, not of a PR, so the producer records `infra-down` and ends the run.
- // That exit shape is why these are not handled like the ABSORBED retirees
+ // Retired states NO surviving state absorbed — retiring each of these moved its inventory
+ // into no successor inbox:
+ // ai:blocked-deploy (issue-pr-cron#225) — not in the machine at all: no label, no
+ // classifier arm, no counts emission, no parking transition. `ai:reject` is not its
+ // successor: a reject is a verdict on the PR's code, not a fact about a deploy, so
+ // no surviving series measures what this one measured.
+ // ai:blocked-infra (issue-pr-cron#108) — infrastructure being down is a property of the
+ // moment, not of a PR, so the producer records `infra-down` and ends the run.
+ // That absence of a successor is why these are not handled like the ABSORBED retirees
// (awaiting-re-vet, ai:relink, human:reject), whose stale cells are ignored outright: an
// absorbed retiree's tokens reappear in the surviving state's own cell (a PR that was
// awaiting-re-vet IS un-vetted), so its cell is a double-count — but a no-successor
@@ -862,7 +903,12 @@ Pipeline state machine
// `prs`) on a `lanes` snapshot, the flat counts key on a pre-`lanes` one; a count that
// arrives with no list surfaces through detailBody's missing-list defect, never as an
// empty queue.
+ //
+ // FALLBACK-ONLY, like STATES: on a snapshot carrying `stateDescriptors` a stale cell
+ // for a retired state is occupancy no descriptor claims, and it surfaces through the
+ // conservation defect band instead of through this table.
const RETIRED_NO_SUCCESSOR = [
+ { owner: "human", state: "ai:blocked-deploy", lane: "producer-blocked", legacy: "blockedDeploy", act: "strip the label", kind: "blk" },
{ owner: "human", state: "ai:blocked-infra", lane: "producer-blocked", legacy: "blockedInfra", act: "strip the label", kind: "blk" },
];
@@ -880,7 +926,305 @@ Pipeline state machine
// the open-issue count nor any age — then the page is exactly what it was, and the legend
// below describes no mark that is not on screen.
const band = openBand();
- if (hasLanes) {
+ if (descriptors) {
+ // ---- Descriptor-driven machine (issue-pr-cron#130 / rain-org-health#164): the tool
+ // emitted the machine's shape as data, so the panel renders exactly that — descriptor
+ // order is render order within each owner group. Every field is UNTRUSTED (the
+ // snapshot is another repo's artifact), so each descriptor is screened on the way in.
+ // A malformed one renders NO state and is REPORTED in the defect band below — never
+ // silently dropped, because a dropped descriptor is a vanished state, the drift this
+ // path exists to kill (any occupancy it meant to claim goes unclaimed, which the
+ // conservation sweep also reports). `kind` is clamped to the three display kinds so
+ // snapshot content can never smuggle a meaningful class (`zero`, `rising`, `sel`)
+ // onto a box; an unknown kind renders with the neutral default border.
+ const KINDS = new Set(["flow", "rule", "blk"]);
+ const OWNER_KEYS = new Set(OWNERS.map((o) => o.key));
+ const readDescriptor = (d) => {
+ if (!d || typeof d !== "object" || typeof d.key !== "string" || !d.key) return null;
+ if (!OWNER_KEYS.has(d.owner)) return null;
+ // `occupancy` is exactly one of {lane} (count + items from that lane's cell, keyed
+ // by the state key — sparse-absent ⇒ 0) or {counts, items, itemsAreIssues} (count
+ // from `counts.`, click-through list from the named top-level array,
+ // `itemsAreIssues` picking which URL path `urlOf` tries first).
+ const occ = d.occupancy;
+ let source = null;
+ if (occ && typeof occ === "object") {
+ if (typeof occ.lane === "string" && occ.lane) {
+ source = { lane: occ.lane };
+ } else if (
+ typeof occ.counts === "string" && occ.counts &&
+ typeof occ.items === "string" && occ.items
+ ) {
+ source = { counts: occ.counts, items: occ.items, issue: !!occ.itemsAreIssues };
+ }
+ }
+ if (!source) return null;
+ return {
+ key: d.key,
+ owner: d.owner,
+ label: typeof d.label === "string" && d.label ? d.label : d.key,
+ act: typeof d.act === "string" ? d.act : "",
+ kind: KINDS.has(d.kind) ? d.kind : "",
+ hist: typeof d.hist === "string" ? d.hist : "",
+ histFold: Array.isArray(d.histFold) ? d.histFold.filter((k) => typeof k === "string") : [],
+ source,
+ };
+ };
+ const defects = [];
+ const parsed = [];
+ // Keys already taken. A state key is the panel's IDENTITY for a state: the box's
+ // `data-t`, and the `register`/`itemsFor` map key the click-to-expand reads. So two
+ // descriptors sharing one key do not merely duplicate a box — the second's
+ // registration OVERWRITES the first's, and the first box then opens onto the
+ // second's list. That is #141's exact failure (a count that opens onto something
+ // that is not its own list) and neither claim check would catch it: with different
+ // occupancy sources the two claims are different ids, so both read as singly
+ // claimed. So a repeat renders NO state and is REPORTED, exactly like a malformed
+ // entry — and, like a malformed entry, its claim is never registered, so the sweep
+ // additionally names the cell or counts key it meant to claim. FIRST occurrence
+ // wins: array order is render order, so the first is the one the tool put first.
+ const seenKeys = new Set();
+ descriptors.forEach((d, i) => {
+ const p = readDescriptor(d);
+ if (!p) {
+ defects.push(
+ "stateDescriptors[" + i + "] is malformed — it renders no state, and occupancy it meant to claim reports as unclaimed",
+ );
+ return;
+ }
+ if (seenKeys.has(p.key)) {
+ defects.push(
+ 'stateDescriptors[' + i + '] repeats the key "' + p.key +
+ '" — it renders no state, and occupancy it meant to claim reports as unclaimed',
+ );
+ return;
+ }
+ seenKeys.add(p.key);
+ parsed.push(p);
+ });
+ // CONSERVATION (the invariant that would have made the blocked-on drift scream
+ // instead of hiding 17 PRs), per the ratified contract's scoping
+ // (issue-pr-cron#130): every `lanes` cell must be claimed by exactly ONE descriptor
+ // — zero exceptions (the tool stopped emitting its one redundant cell in the same
+ // change that added descriptors) — and every `counts` key must be claimed by exactly
+ // one descriptor through the keys it READS (its occupancy `counts` source, or the
+ // `hist` mirror a lane state's series draws from), outside the named frozen-legacy
+ // set below. A counts-source descriptor's declared `counts`/`items` keys must also
+ // RESOLVE in the snapshot. Unclaimed occupancy is inventory the machine renders
+ // NOWHERE; a double claim renders the same inventory twice; an unresolved source is a
+ // count with no list (or no count at all). All are the tool's defects, and all
+ // surface in the defect band.
+ const claims = new Map();
+ const itemClaims = new Map();
+ const claim = (id) => claims.set(id, (claims.get(id) || 0) + 1);
+ // The occupancy a lane-sourced descriptor RENDERS, resolved once so the sweep's
+ // cross-check below and the render loop can never read it two different ways.
+ const laneCount = (p) => {
+ const cell = laneState(p.source.lane, p.key);
+ if (!cell) return 0;
+ // A cell carries its own two numbers: the `count` it STATES and the `prs` it
+ // actually holds. `Number(cell.count) || 0` cannot tell a missing or non-numeric
+ // count from a real zero, so a broken count over a populated list would render 0
+ // while the click expanded onto PRs. Render the LARGER: a count that arrives
+ // broken must never hide inventory the list proves is there, and the box must
+ // agree with what it opens onto. The disagreement itself is reported below.
+ const stated = Number(cell.count) || 0;
+ const listed = Array.isArray(cell.prs) ? cell.prs.length : 0;
+ return Math.max(stated, listed);
+ };
+ // Claim ids are JSON-encoded so no lane/state/counts spelling can collide or forge
+ // another key's id.
+ const laneId = (lane, state) => "lane " + JSON.stringify([lane, state]);
+ const countsId = (k) => "counts " + JSON.stringify(k);
+ for (const p of parsed) {
+ if (p.source.lane) claim(laneId(p.source.lane, p.key));
+ else {
+ if (!Object.prototype.hasOwnProperty.call(c, p.source.counts)) {
+ defects.push(
+ 'descriptor "' + p.key + '" names counts source "' + p.source.counts + '" which does not resolve in the snapshot',
+ );
+ }
+ if (!Array.isArray(hq[p.source.items])) {
+ defects.push(
+ 'descriptor "' + p.key + '" names items source "' + p.source.items + '" which does not resolve in the snapshot',
+ );
+ }
+ }
+ // Every counts key this descriptor READS is claimed — deduped, because a
+ // counts-source descriptor's `hist` usually IS its counts key.
+ //
+ // `histFold` counts too: those keys are where this state's series draws the
+ // ABSORBED retirees' samples from, so their inventory does reach the screen — as
+ // part of this state's line. Leaving them out flags a nonzero absorbed-retiree
+ // count as unclaimed on a perfectly healthy snapshot: the successor draws the past
+ // (`ai:reject` folds `humanReject`/`relink`) while the residue rows carry no `hist`
+ // of their own, precisely so the same history is not rendered twice. Their lane
+ // cells are claimed by those residue descriptors; their counts keys are claimed
+ // here. The invariant stays honest in BOTH directions — exactly one descriptor
+ // claims a fold key, because a fold key must not also be a live row's `hist` (the
+ // producer side is held to that), so a key claimed as both still reads as the
+ // double claim it is.
+ const read = new Set();
+ if (p.source.counts) read.add(p.source.counts);
+ if (p.hist) read.add(p.hist);
+ for (const k of p.histFold) read.add(k);
+ for (const k of read) claim(countsId(k));
+ // The top-level array a counts-source descriptor lists from is claimed too. It is
+ // not swept for absence (an array with no `counts` entry is not occupancy this page
+ // knows how to count), but two descriptors naming the SAME array is a defect the
+ // other checks cannot see: their keys differ, their counts keys may differ, and yet
+ // both boxes open onto one list — a count that opens onto something that is not its
+ // own list, which is #141 again.
+ if (p.source.items) itemClaims.set(p.source.items, (itemClaims.get(p.source.items) || 0) + 1);
+ // ---- The page holds TWO numbers for a lane state and is the only place they meet.
+ // The lane cell is the OCCUPANCY it renders; `hist` names the `counts` key its
+ // SERIES is drawn from — and the tool computes those two ways (issue-pr-cron#228:
+ // `counts.ready` off the label bucket, the lane cell off `classify_lane`). When
+ // they disagree, the box draws one number under a sparkline sitting at the other,
+ // and every claim check passes because each key is claimed exactly once. Only a
+ // comparison catches it, so the page makes the disagreement VISIBLE — it does not
+ // guess which number is right, and it does not drop the claim (every lane state's
+ // `hist` mirrors a counts key, so dropping it would paint false defects across a
+ // healthy snapshot). States that agree stay silent.
+ // The SAME class one level down: a cell's stated `count` against the `prs` it
+ // carries. The sweep cannot catch this — a claimed cell leaves `misclaimed` at
+ // n === 1 — so the box would read one number while its click-through showed
+ // another, silently. Only checked when the list is non-empty: a count over an
+ // EMPTY list is the missing-list case, which detailBody already reports at the
+ // click ("the list is missing, not empty") rather than as a snapshot defect.
+ if (p.source.lane) {
+ const cell = laneState(p.source.lane, p.key);
+ const stated = cell ? Number(cell.count) || 0 : 0;
+ const listed = cell && Array.isArray(cell.prs) ? cell.prs.length : 0;
+ if (listed > 0 && stated !== listed) {
+ defects.push(
+ 'lanes["' + p.source.lane + '"]["' + p.key + '"] states count ' + stated +
+ " but carries " + listed + " PRs — the box renders " + Math.max(stated, listed) +
+ ", the larger, so a broken count cannot hide inventory the list proves is there",
+ );
+ }
+ }
+ if (p.source.lane && p.hist && Object.prototype.hasOwnProperty.call(c, p.hist)) {
+ const shown = laneCount(p);
+ const mirrored = Number(c[p.hist]) || 0;
+ if (shown !== mirrored) {
+ defects.push(
+ 'state "' + p.key + '" is counted two ways and the snapshot disagrees with itself: ' +
+ 'the rendered occupancy lanes["' + p.source.lane + '"]["' + p.key + '"] holds ' + shown +
+ ', while counts["' + p.hist + '"] — the key this box\'s series is drawn from — says ' + mirrored +
+ '. The box renders ' + shown + '; fix the tool\'s emit (issue-pr-cron#228)',
+ );
+ }
+ }
+ }
+
+ OWNERS.forEach((O) => {
+ const own = parsed.filter((p) => p.owner === O.key);
+ if (!own.length) return; // an actor the tool files no state under draws no empty heading
+ const g = el("div", "fsm-grid");
+ let ownerTotal = 0;
+ const boxes = [];
+ own.forEach((p) => {
+ let count;
+ if (p.source.lane) {
+ const cell = laneState(p.source.lane, p.key);
+ count = laneCount(p);
+ register(p.key, cell && cell.prs, false, count);
+ } else {
+ // Registration is UNCONDITIONAL on both branches (#141): a count whose list
+ // never arrived reads as detailBody's missing-list defect, never as an empty
+ // queue.
+ count = Number(c[p.source.counts]) || 0;
+ register(p.key, hq[p.source.items], p.source.issue, count);
+ }
+ ownerTotal += count;
+ // The descriptor names its own series key AND its own retired-key folds —
+ // HIST_FOLD serves only the frozen fallback. An empty histFold folds nothing.
+ const b = stateBox(p.key, p.label, count, p.act, p.kind, seriesFor(p.hist, p.histFold));
+ boxes.push({ b, count, label: p.label });
+ g.append(b);
+ });
+ markLead(boxes);
+ const lane = el("div", "fsm-lane");
+ const h = el("div", "fsm-lane-h");
+ h.append(el("span", null, O.title), el("span", "lsub", O.sub), el("span", "lct", String(ownerTotal)));
+ lane.append(h, g);
+ grid.append(lane);
+ });
+
+ // The conservation sweep, over the snapshot's own occupancy keys. `legacy` marks a
+ // key the ratified contract lets go UNCLAIMED — it never licenses a double claim.
+ const misclaimed = (id, name, held, legacy) => {
+ const n = claims.get(id) || 0;
+ if (n === 1) return;
+ // Zero inventory renders nowhere VACUOUSLY — nothing is hidden. The live tool
+ // still emits drained retired counts keys at 0 (kept-while-nonzero applies to the
+ // descriptor list, not to the keys), so an unclaimed key holding nothing must not
+ // paint a permanent false defect on a healthy snapshot. A DOUBLE claim is
+ // STRUCTURAL — the same inventory wired twice — and stays a defect at any count,
+ // frozen-legacy keys included: two descriptors both drawing `openIssues` would
+ // render the open-issue population as two state series, which no exception covers.
+ if (n === 0 && (!held || legacy)) return;
+ defects.push(
+ name + " (" + held + " held) " +
+ (n === 0
+ ? "is claimed by no descriptor — its inventory renders nowhere"
+ : "is claimed by " + n + " descriptors — its inventory renders " + n + " times"),
+ );
+ };
+ if (hasLanes) {
+ for (const lane of Object.keys(hq.lanes)) {
+ const cells = hq.lanes[lane];
+ if (!cells || typeof cells !== "object") continue;
+ for (const state of Object.keys(cells)) {
+ const cell = cells[state];
+ // What the cell HOLDS is the larger of its stated count and the list it
+ // actually carries: a cell whose `count` was dropped or arrived non-numeric
+ // still holds every PR in `prs`, and scoring that 0 would let the zero-held
+ // carve-out swallow real stranded inventory. This is the one place the sweep
+ // has the list in hand, so it uses it.
+ const held = cell && typeof cell === "object"
+ ? Math.max(Number(cell.count) || 0, Array.isArray(cell.prs) ? cell.prs.length : 0)
+ : 0;
+ misclaimed(laneId(lane, state), 'lanes["' + lane + '"]["' + state + '"]', held);
+ }
+ }
+ }
+ // The FROZEN-LEGACY exception the ratified contract names (issue-pr-cron#130,
+ // 2026-08-06 clarification) — the ONLY counts keys allowed to go unclaimed:
+ // - non-state metrics: population/rollup numbers, not inboxes anyone acts on
+ // (`totalProducerPrs`, `openIssues` — the band above draws it — and
+ // `archivedRepoPrs`);
+ // - `closeCandidateIssues`: the legacy duplicate of inventory the two
+ // close-candidate descriptors already claim.
+ // Any OTHER unclaimed counts key is state-shaped inventory rendering nowhere.
+ const LEGACY_UNCLAIMED = new Set([
+ "totalProducerPrs",
+ "openIssues",
+ "archivedRepoPrs",
+ "closeCandidateIssues",
+ ]);
+ for (const k of Object.keys(c)) {
+ misclaimed(countsId(k), 'counts["' + k + '"]', Number(c[k]) || 0, LEGACY_UNCLAIMED.has(k));
+ }
+ for (const [k, n] of itemClaims) {
+ if (n < 2) continue;
+ defects.push(
+ 'the top-level array "' + k + '" is named by ' + n +
+ " descriptors — their boxes open onto the same list",
+ );
+ }
+ if (defects.length) {
+ const dbox = el("div", "fsm-defect");
+ dbox.append(el(
+ "div",
+ "fd-h",
+ "Snapshot defect — the tool's stateDescriptors do not cover its own occupancy. Fix the tool's emit; nothing here is ignored:",
+ ));
+ for (const t of defects) dbox.append(el("div", "fd-li", t));
+ grid.append(dbox);
+ }
+ } else if (hasLanes) {
OWNERS.forEach((O) => {
const g = el("div", "fsm-grid");
let ownerTotal = 0;
@@ -930,7 +1274,7 @@ Pipeline state machine
grid.append(lane);
});
} else {
- // Backward-compat: an older human-queue.json with no `lanes` — render the 6 states
+ // Backward-compat: an older human-queue.json with no `lanes` — render the 5 states
// the legacy `counts` keys carry (all human-owned under the actor mapping) from
// `counts`, so the panel never blanks.
// `items` names a top-level array as in STATES; an entry with none lists from the flat
@@ -940,7 +1284,6 @@ Pipeline state machine
{ key: "ai:ready", label: "ai:ready", n: c.ready, hist: "ready", act: "merge", kind: "flow" },
{ key: "ai:design", label: "ai:design", n: c.design, hist: "design", act: "rule on design Q", kind: "rule" },
{ key: "closeCandidateIssues", label: "ai:close-candidate (issues)", n: c.closeCandidateIssues, items: "closeCandidateIssues", issue: true, hist: "closeCandidateIssues", act: "close issue", kind: "rule" },
- { key: "ai:blocked-deploy", label: "ai:blocked-deploy", n: c.blockedDeploy, hist: "blockedDeploy", act: "resolve deploy", kind: "blk" },
{ key: "ai:blocked-on", label: "ai:blocked-on", n: c.blockedOn, hist: "blockedOn", act: "merge dependency", kind: "blk" },
];
const g = el("div", "fsm-grid");
@@ -1030,6 +1373,13 @@ Pipeline state machine
);
if (hq.sample) note.append(" ", el("b", null, "(sample data — live once issue-pr-cron#41 lands)"));
note.append(".");
+ if (descriptors) {
+ note.append(
+ " The machine's shape above is the tool's own ",
+ el("code", null, "stateDescriptors"),
+ " — states appear, move between actors, and retire on the tool's say-so, with no edit to this page.",
+ );
+ }
if (hist.length) {
note.append(
" Each state carries an inventory sparkline (trailing 4 weeks); a ",
diff --git a/test/dashboard.test.js b/test/dashboard.test.js
index ecf983d..afc3b83 100644
--- a/test/dashboard.test.js
+++ b/test/dashboard.test.js
@@ -1387,8 +1387,10 @@ Deno.test("pipeline FSM: states group under the three actor headings, no fourth
"ai:design": { count: 1, prs: [] },
"ai:close-candidate": { count: 1, prs: [] },
},
+ // Drained residue of a state the machine no longer has (issue-pr-cron#225): at zero
+ // it draws no box at all. Nonzero it would draw a straggler — its own tests below.
"producer-blocked": {
- "ai:blocked-deploy": { count: 1, prs: [] },
+ "ai:blocked-deploy": { count: 0, prs: [] },
},
"human-decisions": {
"human:reject": { count: 1, prs: [] },
@@ -1459,12 +1461,18 @@ Deno.test("pipeline FSM: states group under the three actor headings, no fourth
!human.states.includes("ai:blocked-on"),
`ai:blocked-on is not in the human's inbox: ${JSON.stringify(human.states)}`,
);
+ // ai:blocked-deploy is no longer a modeled state (issue-pr-cron#225), so the drained cell
+ // in the fixture files under NO actor — it is not a human inbox with nothing in it.
+ assert(
+ groups.every((g) => !g.states.includes("ai:blocked-deploy")),
+ `the drained ai:blocked-deploy residue renders nowhere: ${JSON.stringify(groups.map((g) => g.states))}`,
+ );
// Every state box sits under exactly one heading (no leaks into a fourth group).
- // 11 = every STATES entry filed once: producer 2 (ai:reject, the untouched backlog),
- // vetter 3 (un-vetted, the unvetted flags, ai:blocked-on), human 6 (leak, ai:ready,
- // ai:design, ai:blocked-deploy, human:design, the upheld flags).
+ // 10 = every STATES entry filed once: producer 2 (ai:reject, the untouched backlog),
+ // vetter 3 (un-vetted, the unvetted flags, ai:blocked-on), human 5 (leak, ai:ready,
+ // ai:design, human:design, the upheld flags).
const total = groups.reduce((n, g) => n + g.states.length, 0);
- assert(total === 11, `all 11 states filed once: ${total}`);
+ assert(total === 10, `all 10 states filed once: ${total}`);
// Both directions, same as awaiting-re-vet: a reintroduction fails here. Each retired
// lane key in the fixture above is a DECOY — a stale snapshot still carrying it must
// render it in NO group.
@@ -1728,13 +1736,12 @@ Deno.test("fsm history: a junk count is skipped like an absent one and cannot po
assert(ys[0] === ys[1], `both samples read 6, so the line is flat: ${JSON.stringify(ys)}`);
});
-// #69: the historically dual-owner states each resolve to ONE actor.
+// #69: a historically dual-owner state resolves to ONE actor.
Deno.test("pipeline FSM: ambiguous states resolve to a single owner", () => {
const box = fsmBox({
counts: { leaks: 0, ready: 0, closeCandidateIssues: 0 },
lanes: {
"producer-blocked": {
- "ai:blocked-deploy": { count: 1, prs: [] },
"ai:blocked-on": { count: 1, prs: [] },
},
"human-decisions": { "human:reject": { count: 1, prs: [] } },
@@ -1752,11 +1759,6 @@ Deno.test("pipeline FSM: ambiguous states resolve to a single owner", () => {
owner("human:reject") === null,
`retired human:reject renders in no group: ${owner("human:reject")}`,
);
- // ai:blocked-deploy → human (the actor that actually unblocks it).
- assert(
- (owner("ai:blocked-deploy") || "").includes("Human action"),
- `ai:blocked-deploy is human-owned: ${owner("ai:blocked-deploy")}`,
- );
// ai:blocked-on → vetter (issue-pr-cron#161): the vetter's state-load clears the flag
// the run after every typed dep merges/closes. This fixture keys the cell under the
// pre-re-key `producer-blocked` lane, so the box surfacing at all is the stale-lane
@@ -1863,186 +1865,267 @@ Deno.test("pipeline FSM: a stale producer-blocked blocked-on cell still surfaces
);
});
-// issue-pr-cron#108: `ai:blocked-infra` is retired with NO successor state — infra being
-// down is a property of the moment, not of a PR, so the producer records `infra-down` and
-// ends the run. Unlike the absorbed retirees (awaiting-re-vet, ai:relink, human:reject),
-// whose tokens reappear in a surviving state's own cell, a PR still carrying this label
-// reappears NOWHERE — so a snapshot that still counts the state (an old snapshot, or the
-// label reapplied by hand) surfaces as a straggler box in the human group instead of
-// vanishing from the board.
-Deno.test("pipeline FSM: a nonzero ai:blocked-infra count surfaces as a human-owned straggler box", () => {
+// The retired states NO surviving state absorbed. `ai:blocked-deploy`
+// (issue-pr-cron#225) is DELETED from the machine — no label, no classifier arm, no counts
+// emission, no parking transition — and `ai:reject` is not its successor (a reject is a
+// verdict on the PR's code, not a fact about a deploy). `ai:blocked-infra`
+// (issue-pr-cron#108) exits by the run ending: infra being down is a property of the
+// moment, not of a PR, so the producer records `infra-down` and stops. Unlike the ABSORBED
+// retirees (awaiting-re-vet, ai:relink, human:reject), whose tokens reappear in a surviving
+// state's own cell, a PR still carrying either label reappears NOWHERE — so a snapshot that
+// still counts one (an old snapshot, or the label reapplied by hand) surfaces as a
+// straggler box in the human group instead of vanishing from the board. Both are held to
+// the same behaviour here, because it is one mechanism: the entries in
+// `RETIRED_NO_SUCCESSOR`, not per-state special-casing.
+const NO_SUCCESSOR = [
+ { state: "ai:blocked-deploy", legacy: "blockedDeploy" },
+ { state: "ai:blocked-infra", legacy: "blockedInfra" },
+];
+
+Deno.test("pipeline FSM: a nonzero no-successor retiree count surfaces as a human-owned straggler box", () => {
const now = Date.parse("2026-08-06T00:00:00Z");
- // History still carrying blockedInfra samples draws NOTHING for it: the series has no
- // surviving state to fold into and the straggler box carries no chart.
- const history = [
- { t: now - 2 * DAY, counts: { ready: 1, blockedInfra: 5 } },
- { t: now - DAY, counts: { ready: 1, blockedInfra: 4 } },
- { t: now, counts: { ready: 1, blockedInfra: 2 } },
- ];
+ for (const { state, legacy } of NO_SUCCESSOR) {
+ // History still carrying the retired key draws NOTHING for it: the series has no
+ // surviving state to fold into and the straggler box carries no chart.
+ const history = [
+ { t: now - 2 * DAY, counts: { ready: 1, [legacy]: 5 } },
+ { t: now - DAY, counts: { ready: 1, [legacy]: 4 } },
+ { t: now, counts: { ready: 1, [legacy]: 2 } },
+ ];
+ const box = fsmBox({
+ counts: { leaks: 0, ready: 1, [legacy]: 2 },
+ lanes: {
+ "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } },
+ "producer-blocked": {
+ [state]: {
+ count: 2,
+ prs: [
+ { repo: "rainlanguage/rain.flow", number: 12, url: "https://github.com/rainlanguage/rain.flow/pull/12", title: `stale ${state} straggler` },
+ { repo: "rainlanguage/rain.dia", number: 34, url: "https://github.com/rainlanguage/rain.dia/pull/34", title: "hand relabelled" },
+ ],
+ },
+ },
+ },
+ }, history);
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === state);
+ assert(b, `${state}: the straggler box renders while the count is nonzero`);
+ assert(
+ collect(box, "fsm-state").filter((x) => x.dataset.t === state).length === 1,
+ `${state}: exactly one straggler box on the whole board`,
+ );
+ assert(
+ collect(b, "sc")[0].textContent === "2",
+ `${state}: it carries the snapshot's count: ${collect(b, "sc")[0].textContent}`,
+ );
+ assert(
+ collect(b, "sa")[0].textContent === "strip the label",
+ `${state}: the act clears the residue: ${collect(b, "sa")[0].textContent}`,
+ );
+ assert(b.className.includes("blk"), `${state}: a stuck pile, kind blk: ${b.className}`);
+ assert(
+ collect(b, "fsm-spark").length === 0,
+ `${state}: no sparkline — the retired series is drawn nowhere`,
+ );
+ // Filed under the human — the actor who strips the label — and counted in that total
+ // (1 ready + 2 stragglers).
+ const human = ownerGroups(box).find((g) => g.title.includes("Human action"));
+ assert(
+ human.states.includes(state),
+ `${state}: the straggler files under the human: ${JSON.stringify(human.states)}`,
+ );
+ assert(human.title.endsWith("3"), `${state}: the human total counts it: ${human.title}`);
+ // Click-through lists the stragglers like any state box.
+ b.click();
+ const text = textOf(box);
+ assert(text.includes(`stale ${state} straggler`), `${state}: first straggler PR listed`);
+ assert(text.includes("rain.dia#34"), `${state}: second straggler PR listed by repo#number`);
+ }
+});
+
+// Both retirees at once, on ONE snapshot: two distinct boxes, each opening onto its OWN
+// list. They share a lane (`producer-blocked`) and an act, so a mechanism that keyed the
+// residue by lane, or registered one list for the pile, would render one box or cross the
+// click-throughs — and the total would silently lose a straggler either way.
+Deno.test("pipeline FSM: two no-successor retirees on one snapshot render as two independent stragglers", () => {
const box = fsmBox({
- counts: { leaks: 0, ready: 1, blockedInfra: 2 },
+ counts: { leaks: 0, ready: 1, blockedDeploy: 1, blockedInfra: 2 },
lanes: {
"vetter-verdicts": { "ai:ready": { count: 1, prs: [] } },
"producer-blocked": {
+ "ai:blocked-deploy": {
+ count: 1,
+ prs: [{ repo: "rainlanguage/rain.flow", number: 12, url: "https://github.com/rainlanguage/rain.flow/pull/12", title: "deploy residue" }],
+ },
"ai:blocked-infra": {
count: 2,
prs: [
- { repo: "rainlanguage/rain.flow", number: 12, url: "https://github.com/rainlanguage/rain.flow/pull/12", title: "stale infra straggler" },
- { repo: "rainlanguage/rain.dia", number: 34, url: "https://github.com/rainlanguage/rain.dia/pull/34", title: "hand relabelled" },
+ { repo: "rainlanguage/rain.dia", number: 34, url: "https://github.com/rainlanguage/rain.dia/pull/34", title: "infra residue A" },
+ { repo: "rainlanguage/rain.flare", number: 56, url: "https://github.com/rainlanguage/rain.flare/pull/56", title: "infra residue B" },
],
},
},
},
- }, history);
- const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-infra");
- assert(b, "the straggler box renders while the count is nonzero");
- assert(
- collect(box, "fsm-state").filter((x) => x.dataset.t === "ai:blocked-infra").length === 1,
- "exactly one straggler box on the whole board",
- );
- assert(
- collect(b, "sc")[0].textContent === "2",
- `it carries the snapshot's count: ${collect(b, "sc")[0].textContent}`,
- );
+ });
+ const at = (k) => collect(box, "fsm-state").filter((x) => x.dataset.t === k);
+ assert(at("ai:blocked-deploy").length === 1, "one deploy straggler");
+ assert(at("ai:blocked-infra").length === 1, "one infra straggler");
assert(
- collect(b, "sa")[0].textContent === "strip the label",
- `the act clears the residue: ${collect(b, "sa")[0].textContent}`,
+ collect(at("ai:blocked-deploy")[0], "sc")[0].textContent === "1" &&
+ collect(at("ai:blocked-infra")[0], "sc")[0].textContent === "2",
+ "each carries its own count, never the lane's sum",
);
- assert(b.className.includes("blk"), `a stuck pile, kind blk: ${b.className}`);
+ // 1 ready + 1 deploy + 2 infra: both stragglers land in the human total.
+ const human = ownerGroups(box).find((g) => g.title.includes("Human action"));
+ assert(human.title.endsWith("4"), `both stragglers count toward the human total: ${human.title}`);
+ // Each click-through opens onto its own rows, never the other's.
+ at("ai:blocked-deploy")[0].click();
+ const deployText = textOf(box);
assert(
- collect(b, "fsm-spark").length === 0,
- "no sparkline: the retired series is drawn nowhere",
+ deployText.includes("deploy residue") && !deployText.includes("infra residue"),
+ `the deploy box lists only its own PRs: ${deployText}`,
);
- // Filed under the human — the actor who strips the label — and counted in that total
- // (1 ready + 2 stragglers).
- const human = ownerGroups(box).find((g) => g.title.includes("Human action"));
+ at("ai:blocked-deploy")[0].click();
+ at("ai:blocked-infra")[0].click();
+ const infraText = textOf(box);
assert(
- human.states.includes("ai:blocked-infra"),
- `the straggler files under the human: ${JSON.stringify(human.states)}`,
+ infraText.includes("infra residue A") && infraText.includes("infra residue B") &&
+ !infraText.includes("deploy residue"),
+ `the infra box lists only its own PRs: ${infraText}`,
);
- assert(human.title.endsWith("3"), `the human total counts it: ${human.title}`);
- // Click-through lists the stragglers like any state box.
- b.click();
- const text = textOf(box);
- assert(text.includes("stale infra straggler"), "first straggler PR listed");
- assert(text.includes("rain.dia#34"), "second straggler PR listed by repo#number");
});
// The straggler renders ONLY while a snapshot still counts the state. Absent — the live
// shape, the tool no longer emits it — draws nothing, and so does an emitter that still
// writes the cell at zero: a retired state earns no permanent dimmed box describing a
// machine that no longer exists (rain-org-health#145).
-Deno.test("pipeline FSM: a zero or absent ai:blocked-infra count draws no straggler box", () => {
- const none = (box, what) => {
+Deno.test("pipeline FSM: a zero or absent no-successor retiree count draws no straggler box", () => {
+ const none = (box, state, what) => {
assert(
- !collect(box, "fsm-state").some((x) => x.dataset.t === "ai:blocked-infra"),
- `no box is keyed to the retired state (${what})`,
+ !collect(box, "fsm-state").some((x) => x.dataset.t === state),
+ `no box is keyed to ${state} (${what})`,
);
assert(
- !collect(box, "sk").some((s) => s.textContent === "ai:blocked-infra"),
- `no box is labelled with the retired state (${what})`,
+ !collect(box, "sk").some((s) => s.textContent === state),
+ `no box is labelled ${state} (${what})`,
);
};
- none(
- fsmBox({
- counts: { leaks: 0, ready: 1 },
- lanes: { "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } } },
- }),
- "absent",
- );
- none(
- fsmBox({
- counts: { leaks: 0, ready: 0, blockedInfra: 0 },
- lanes: { "producer-blocked": { "ai:blocked-infra": { count: 0, prs: [] } } },
- }),
- "zero",
- );
+ // The live shape: neither key emitted at all.
+ const absent = fsmBox({
+ counts: { leaks: 0, ready: 1 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } } },
+ });
+ for (const { state } of NO_SUCCESSOR) none(absent, state, "absent");
+ // An emitter that still writes the cell and the counts key, drained to zero.
+ for (const { state, legacy } of NO_SUCCESSOR) {
+ none(
+ fsmBox({
+ counts: { leaks: 0, ready: 0, [legacy]: 0 },
+ lanes: { "producer-blocked": { [state]: { count: 0, prs: [] } } },
+ }),
+ state,
+ "zero",
+ );
+ }
});
-// A `lanes` snapshot can carry the COUNT without the cell (`counts.blockedInfra` set, no
-// `lanes` cell — the lanes object is sparse). The count still surfaces, and the click
-// reads as detailBody's missing-list defect rather than an empty queue: a count with no
-// list is #141's exact signature.
-Deno.test("pipeline FSM: a counts-only ai:blocked-infra straggler surfaces and reports its missing list", () => {
- const box = fsmBox({
- counts: { leaks: 0, ready: 0, blockedInfra: 4 },
- lanes: { "vetter-verdicts": { "ai:ready": { count: 0, prs: [] } } },
- });
- const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-infra");
- assert(b, "the counts-only straggler still renders");
- assert(
- collect(b, "sc")[0].textContent === "4",
- `it carries the counts key: ${collect(b, "sc")[0].textContent}`,
- );
- b.click();
- const text = textOf(box);
- assert(
- text.includes("the list is missing, not empty"),
- `a count with no list reads as the missing-list defect: ${text}`,
- );
+// A `lanes` snapshot can carry the COUNT without the cell (the counts key set, no `lanes`
+// cell — the lanes object is sparse). The count still surfaces, and the click reads as
+// detailBody's missing-list defect rather than an empty queue: a count with no list is
+// #141's exact signature.
+Deno.test("pipeline FSM: a counts-only no-successor straggler surfaces and reports its missing list", () => {
+ for (const { state, legacy } of NO_SUCCESSOR) {
+ const box = fsmBox({
+ counts: { leaks: 0, ready: 0, [legacy]: 4 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 0, prs: [] } } },
+ });
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === state);
+ assert(b, `${state}: the counts-only straggler still renders`);
+ assert(
+ collect(b, "sc")[0].textContent === "4",
+ `${state}: it carries the counts key: ${collect(b, "sc")[0].textContent}`,
+ );
+ b.click();
+ const text = textOf(box);
+ assert(
+ text.includes("the list is missing, not empty"),
+ `${state}: a count with no list reads as the missing-list defect: ${text}`,
+ );
+ }
});
// A pre-`lanes` snapshot carries the count under the flat legacy key and its items under
// the flat `states` map — the straggler surfaces from those, through the same legacy
// branch that draws the surviving states.
-Deno.test("pipeline FSM: a pre-lanes snapshot with a nonzero blockedInfra count surfaces the straggler", () => {
+Deno.test("pipeline FSM: a pre-lanes snapshot with a nonzero no-successor count surfaces the straggler", () => {
const now = Date.parse("2026-08-06T00:00:00Z");
- const history = [
- { t: now - DAY, counts: { ready: 1, blockedInfra: 6 } },
- { t: now, counts: { ready: 1, blockedInfra: 3 } },
- ];
- const box = fsmBox({
- counts: { leaks: 0, ready: 1, blockedInfra: 3 },
- states: {
- "ai:blocked-infra": [
- { repo: "rainlanguage/rain.flare", number: 5, url: "https://github.com/rainlanguage/rain.flare/pull/5", title: "legacy straggler" },
- ],
- },
- }, history);
- const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-infra");
- assert(b, "the straggler renders from the legacy counts key");
- assert(
- collect(b, "sc")[0].textContent === "3",
- `it carries the legacy count: ${collect(b, "sc")[0].textContent}`,
- );
- assert(
- collect(b, "fsm-spark").length === 0,
- "no sparkline on the legacy straggler either",
- );
- b.click();
- const text = textOf(box);
- assert(text.includes("rain.flare#5"), "click lists the legacy stragglers");
- // A legacy snapshot without the key draws nothing.
+ for (const { state, legacy } of NO_SUCCESSOR) {
+ const history = [
+ { t: now - DAY, counts: { ready: 1, [legacy]: 6 } },
+ { t: now, counts: { ready: 1, [legacy]: 3 } },
+ ];
+ const box = fsmBox({
+ counts: { leaks: 0, ready: 1, [legacy]: 3 },
+ states: {
+ [state]: [
+ { repo: "rainlanguage/rain.flare", number: 5, url: "https://github.com/rainlanguage/rain.flare/pull/5", title: "legacy straggler" },
+ ],
+ },
+ }, history);
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === state);
+ assert(b, `${state}: the straggler renders from the legacy counts key`);
+ assert(
+ collect(b, "sc")[0].textContent === "3",
+ `${state}: it carries the legacy count: ${collect(b, "sc")[0].textContent}`,
+ );
+ assert(
+ collect(b, "fsm-spark").length === 0,
+ `${state}: no sparkline on the legacy straggler either`,
+ );
+ b.click();
+ assert(textOf(box).includes("rain.flare#5"), `${state}: click lists the legacy stragglers`);
+ }
+ // A legacy snapshot without either key draws nothing.
const clean = fsmBox({ counts: { leaks: 0, ready: 1 } });
- assert(
- !collect(clean, "fsm-state").some((x) => x.dataset.t === "ai:blocked-infra"),
- "an absent legacy key draws no straggler",
- );
+ for (const { state } of NO_SUCCESSOR) {
+ assert(
+ !collect(clean, "fsm-state").some((x) => x.dataset.t === state),
+ `an absent legacy key draws no ${state} straggler`,
+ );
+ }
});
-// `blockedInfra` has NO HIST_FOLD entry: no surviving state absorbed its inventory (the
-// exit was the run ending), so a surviving series must read its OWN samples only — folding
-// the retired key in would fabricate a step in a series that never held that work.
-Deno.test("fsm history: retired blockedInfra samples fold into NO surviving series", () => {
+// Neither `blockedDeploy` nor `blockedInfra` has a HIST_FOLD entry: no surviving state
+// absorbed either inventory, so a surviving series must read its OWN samples only —
+// folding a retired key in would fabricate a step in a series that never held that work.
+// `ai:reject` is the discriminating target: it is where the PRs that carried
+// `ai:blocked-deploy` now sit, which is exactly the resemblance that tempts a fold, and a
+// relabel is not an absorption — a reject measures a verdict on the PR's code, never a
+// deploy.
+Deno.test("fsm history: retired no-successor samples fold into NO surviving series", () => {
const now = Date.parse("2026-08-06T00:00:00Z");
const at = (d) => now - d * DAY;
- // blocked-deploy holds flat at 2 while blockedInfra swings 9 → 0. Unfolded, deploy's
- // line is FLAT; any fold would step it 11 → 2.
+ // reject holds flat at 2 while both retired keys swing to 0. Unfolded, reject's line is
+ // FLAT; folding blockedDeploy would step it 11 → 2, blockedInfra 9 → 2, both 18 → 2.
const history = [
- { t: at(1), counts: { blockedDeploy: 2, blockedInfra: 9 } },
- { t: at(0), counts: { blockedDeploy: 2, blockedInfra: 0 } },
+ { t: at(1), counts: { reject: 2, ready: 2, blockedDeploy: 9, blockedInfra: 7 } },
+ { t: at(0), counts: { reject: 2, ready: 2, blockedDeploy: 0, blockedInfra: 0 } },
];
const box = fsmBox({
- counts: { leaks: 0, ready: 0 },
- lanes: { "producer-blocked": { "ai:blocked-deploy": { count: 2, prs: [] } } },
+ counts: { leaks: 0, ready: 2 },
+ lanes: {
+ "vetter-verdicts": { "ai:reject": { count: 2, prs: [] }, "ai:ready": { count: 2, prs: [] } },
+ },
}, history);
- const deploy = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-deploy");
- const points = tags(deploy, "polyline")[0].getAttribute("points");
- const ys = points.split(" ").map((p) => Number(p.split(",")[1]));
- assert(
- ys[0] === ys[1],
- `deploy reads its own flat 2s, untouched by the retired key: ${points}`,
- );
+ // Every surviving series that draws a line reads flat — no state anywhere absorbs them.
+ for (const key of ["ai:reject", "ai:ready"]) {
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === key);
+ const points = tags(b, "polyline")[0].getAttribute("points");
+ const ys = points.split(" ").map((p) => Number(p.split(",")[1]));
+ assert(
+ ys[0] === ys[1],
+ `${key} reads its own flat 2s, untouched by the retired keys: ${points}`,
+ );
+ }
});
// ---- pipeline.html: Theory-of-Constraints flow layer (#32) ----
@@ -3001,8 +3084,11 @@ Deno.test("pipeline FSM: every box's count equals the number of rows it expands
// ai:design deliberately absent: a sparse lane must render a zero box that
// expands to zero rows, which is the same invariant at n = 0.
},
+ // A no-successor retiree still counted by an old snapshot: it draws a STRAGGLER box,
+ // which is held to this invariant like any other — a residue count that opened onto
+ // someone else's rows would be #141 all over again, in the one place added last.
"producer-blocked": { "ai:blocked-deploy": laneCell("deploy", 3) },
- // Retired state in the data (old snapshot): must render NO box and NO rows, so it
+ // Absorbed retiree in the data (old snapshot): must render NO box and NO rows, so it
// cannot create a count/rows mismatch (issue-pr-cron#133/#138).
"human-decisions": { "human:reject": laneCell("hreject", 4) },
},
@@ -3023,7 +3109,12 @@ Deno.test("pipeline FSM: every box's count equals the number of rows it expands
if (rows !== n) mismatches.push(`${b.dataset.t}: box ${n}, panel ${rows}`);
b.click();
}
+ // 11 = the 10 STATES entries + the ai:blocked-deploy straggler the stale cell surfaces.
assert(checked === 11, `the whole machine was walked, got ${checked} boxes`);
+ assert(
+ box.querySelectorAll("[data-t]").some((b) => b.dataset.t === "ai:blocked-deploy"),
+ "the straggler is one of the boxes walked, not skipped by the sweep",
+ );
// Guard the guard: a fixture that zeroed everything would satisfy the invariant vacuously.
assert(nonZero >= 8, `the fixture must exercise non-zero states, got ${nonZero}`);
assert(
@@ -3113,6 +3204,1050 @@ Deno.test("pipeline FSM: a malformed item renders as a malformed row, taking no
);
});
+// ---- rain-org-health#164: the machine rendered from emitted stateDescriptors ----------
+//
+// The consumer half of issue-pr-cron#130. A snapshot carrying `stateDescriptors` IS the
+// machine: owner grouping, box (key/label/act/kind), occupancy from the descriptor's
+// declared source, history folds from `histFold`, descriptor order as render order. The
+// hand-written STATES table is the FROZEN fallback for descriptor-less snapshots only.
+// Fixtures carry DECOY values on the source a state must NOT read (a counts mirror beside
+// a lane cell), so a wrong-source read changes a rendered number instead of passing.
+
+// A canonical descriptor snapshot: both occupancy kinds, a re-laned state (ai:blocked-on
+// under the VETTER reading `vet-lifecycle` — the exact drift the frozen table could not
+// follow), and a sparse-absent lane state.
+function descHq() {
+ return {
+ counts: {
+ // The lane states' `hist` mirrors, AGREEING with their cells — a self-consistent
+ // snapshot. A mirror that contradicts its cell is itself a reported defect now
+ // (the cross-check), so the wrong-source read is discriminated there rather than
+ // by a decoy that would make every fixture using descHq render a defect band.
+ ready: 5,
+ blockedOn: 3,
+ uncoveredIssues: 4,
+ leaks: 2,
+ },
+ lanes: {
+ "vetter-verdicts": { "ai:ready": { count: 5, prs: fcItems("ready", 5) } },
+ "vet-lifecycle": { "ai:blocked-on": { count: 3, prs: fcItems("blocked", 3) } },
+ },
+ uncoveredIssues: fcItems("uncovered", 4),
+ leaks: fcItems("leak", 2),
+ stateDescriptors: [
+ {
+ key: "uncoveredIssues",
+ owner: "producer",
+ act: "open a PR",
+ kind: "flow",
+ hist: "uncoveredIssues",
+ histFold: [],
+ occupancy: { counts: "uncoveredIssues", items: "uncoveredIssues", itemsAreIssues: true },
+ label: "untouched (no PR)",
+ },
+ {
+ key: "ai:blocked-on",
+ owner: "vetter",
+ act: "clears when every typed dep merges/closes",
+ kind: "blk",
+ hist: "blockedOn",
+ histFold: [],
+ occupancy: { lane: "vet-lifecycle" },
+ },
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ // Sparse-absent lane cell (renders 0, dimmed) deliberately BETWEEN two non-zero
+ // states: descriptor order is NOT count order, so an ordering mutant cannot pass by
+ // sorting.
+ { key: "ai:design", owner: "human", act: "rule on design Q", kind: "rule", hist: "design", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ { key: "leak", owner: "human", act: "model it", kind: "blk", hist: "leaks", histFold: [], occupancy: { counts: "leaks", items: "leaks" } },
+ ],
+ };
+}
+
+Deno.test("pipeline FSM: stateDescriptors drive the machine — owner grouping, act, count, and descriptor order", () => {
+ const box = fsmBox(descHq());
+ // Exactly the descriptor states render, in owner-group order then descriptor order —
+ // and NONE of the frozen table's other boxes (un-vetted, closeCandidateUpheld, …).
+ const keys = box.querySelectorAll("[data-t]").map((b) => b.dataset.t);
+ assert(
+ JSON.stringify(keys) ===
+ JSON.stringify(["uncoveredIssues", "ai:blocked-on", "ai:ready", "ai:design", "leak"]),
+ `descriptor order is render order, and only descriptor states render: ${JSON.stringify(keys)}`,
+ );
+ const groups = ownerGroups(box);
+ assert(groups.length === 3, `three actor groups: ${groups.length}`);
+ const [producer, vetter, human] = groups;
+ assert(
+ JSON.stringify(producer.states) === JSON.stringify(["untouched (no PR)"]),
+ `producer files the descriptor's own states: ${JSON.stringify(producer.states)}`,
+ );
+ // The re-laned state files under the VETTER — the descriptor's owner, not the frozen
+ // table's human resolution.
+ assert(
+ JSON.stringify(vetter.states) === JSON.stringify(["ai:blocked-on"]),
+ `blocked-on is vetter-owned by descriptor: ${JSON.stringify(vetter.states)}`,
+ );
+ assert(
+ JSON.stringify(human.states) === JSON.stringify(["ai:ready", "ai:design", "leak"]),
+ `human states in descriptor order: ${JSON.stringify(human.states)}`,
+ );
+ // Counts come from each descriptor's DECLARED source: the vet-lifecycle cell (3), never
+ // the counts mirror (99) and never the frozen table's producer-blocked lane (absent).
+ const byT = (k) => box.querySelectorAll("[data-t]").find((b) => b.dataset.t === k);
+ assert(
+ collect(byT("ai:blocked-on"), "sc")[0].textContent === "3",
+ `blocked-on reads its declared lane cell: ${collect(byT("ai:blocked-on"), "sc")[0].textContent}`,
+ );
+ assert(vetter.title.endsWith("3"), `the vetter total is the cell's count: ${vetter.title}`);
+ assert(
+ collect(byT("ai:ready"), "sc")[0].textContent === "5",
+ `ready reads its lane cell: ${collect(byT("ai:ready"), "sc")[0].textContent}`,
+ );
+ assert(
+ collect(byT("uncoveredIssues"), "sc")[0].textContent === "4",
+ `a counts-source state reads counts.: ${collect(byT("uncoveredIssues"), "sc")[0].textContent}`,
+ );
+ // act + kind ride the descriptor.
+ assert(
+ collect(byT("ai:blocked-on"), "sa")[0].textContent === "clears when every typed dep merges/closes",
+ `the act is the descriptor's: ${collect(byT("ai:blocked-on"), "sa")[0].textContent}`,
+ );
+ assert(
+ byT("ai:blocked-on").className.split(" ").includes("blk"),
+ `the kind is the descriptor's: ${byT("ai:blocked-on").className}`,
+ );
+ // Sparse-absent lane cell ⇒ 0, dimmed — the existing empty-state language.
+ assert(collect(byT("ai:design"), "sc")[0].textContent === "0", "sparse-absent reads 0");
+ assert(
+ byT("ai:design").className.split(" ").includes("zero"),
+ `a zero descriptor state dims like any other: ${byT("ai:design").className}`,
+ );
+ // Everything claimed ⇒ no conservation band.
+ assert(collect(box, "fsm-defect").length === 0, "a conserved snapshot draws no defect band");
+});
+
+Deno.test("pipeline FSM: descriptor occupancy reads its declared source — lane prs and counts+items alike, itemsAreIssues picking the path", () => {
+ const box = fsmBox(descHq());
+ const detail = box.querySelectorAll("#fsmdetail")[0];
+ const byT = (k) => box.querySelectorAll("[data-t]").find((b) => b.dataset.t === k);
+
+ // Lane source: the cell's own prs, linked as PRs.
+ byT("ai:blocked-on").click();
+ assert(
+ collect(detail, "li").length === 3,
+ `the lane cell's 3 prs list: ${collect(detail, "li").length}`,
+ );
+ assert(textOf(detail).includes("blocked 1"), `the cell's OWN items: ${textOf(detail)}`);
+ assert(
+ collect(detail, "li")[0].href === "https://github.com/o/blocked/pull/1",
+ `lane items link as PRs: ${collect(detail, "li")[0].href}`,
+ );
+ byT("ai:blocked-on").click();
+
+ // Counts source, itemsAreIssues: true — the /issues/ path is tried first.
+ byT("uncoveredIssues").click();
+ assert(
+ collect(detail, "li").length === 4,
+ `the named top-level array lists: ${collect(detail, "li").length}`,
+ );
+ assert(
+ collect(detail, "li")[0].href === "https://github.com/o/uncovered/issues/1",
+ `itemsAreIssues links as issues: ${collect(detail, "li")[0].href}`,
+ );
+ byT("uncoveredIssues").click();
+
+ // Counts source, itemsAreIssues absent — items link as PRs.
+ byT("leak").click();
+ assert(
+ collect(detail, "li")[0].href === "https://github.com/o/leak/pull/1",
+ `absent itemsAreIssues links as PRs: ${collect(detail, "li")[0].href}`,
+ );
+ byT("leak").click();
+
+ // A counts-source descriptor whose named array never arrived: the count still renders
+ // and the click reads as detailBody's missing-list defect — never as an empty queue.
+ const noList = descHq();
+ delete noList.leaks;
+ const box2 = fsmBox(noList);
+ const detail2 = box2.querySelectorAll("#fsmdetail")[0];
+ box2.querySelectorAll("[data-t]").find((b) => b.dataset.t === "leak").click();
+ assert(
+ textOf(detail2).includes("missing, not empty"),
+ `a count with no list reads as the missing-list defect: ${textOf(detail2)}`,
+ );
+});
+
+// The part a naive consumer gets wrong (the schema contract calls it out): retired history
+// keys fold into the descriptor's series per ITS histFold — generic keys with no HIST_FOLD
+// entry prove the fold is the descriptor's, not the hand Map's.
+Deno.test("pipeline FSM: a descriptor's histFold folds retired-key samples into its series", () => {
+ const now = Date.parse("2026-08-06T00:00:00Z");
+ const at = (d) => now - d * DAY;
+ // Real inventory flat at 10 across a collapse: split 1 + 9 before, one key after.
+ // Folded ⇒ flat line; unfolded ⇒ a 1 → 10 cliff and a false bottleneck.
+ const history = [
+ { t: at(4), counts: { current: 1, retiredKey: 9 } },
+ { t: at(3), counts: { current: 1, retiredKey: 9 } },
+ { t: at(2), counts: { current: 1, retiredKey: 9 } },
+ { t: at(1), counts: { current: 10 } },
+ { t: at(0), counts: { current: 10 } },
+ ];
+ const box = fsmBox({
+ counts: {},
+ lanes: { "vet-lifecycle": { st: { count: 10, prs: [] } } },
+ stateDescriptors: [
+ { key: "st", owner: "vetter", act: "vet", kind: "flow", hist: "current", histFold: ["retiredKey"], occupancy: { lane: "vet-lifecycle" } },
+ ],
+ }, history);
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === "st");
+ assert(b, "the descriptor box renders");
+ const line = tags(b, "polyline")[0];
+ assert(line, "the folded series draws a line");
+ const ys = line.getAttribute("points").split(" ").map((p) => Number(p.split(",")[1]));
+ assert(ys.length === 5, `all five samples plot: ${ys.length}`);
+ assert(
+ ys.every((y) => y === ys[0]),
+ `the folded line is continuous, no cliff at the collapse: ${JSON.stringify(ys)}`,
+ );
+ assert(!b.classList.contains("rising"), "a folded flat inventory raises no bottleneck flag");
+});
+
+Deno.test("pipeline FSM: a descriptor's empty histFold folds nothing — the hand-maintained fallback Map never applies", () => {
+ const now = Date.parse("2026-08-06T00:00:00Z");
+ const at = (d) => now - d * DAY;
+ // `reject` HAS a HIST_FOLD entry (humanReject folds in on the frozen fallback). A
+ // descriptor naming hist "reject" with histFold [] must read ONLY reject: flat 28.
+ // Falling back to the Map would draw 70, 70, 28 — a cliff the tool never declared.
+ const history = [
+ { t: at(2), counts: { reject: 28, humanReject: 42 } },
+ { t: at(1), counts: { reject: 28, humanReject: 42 } },
+ { t: at(0), counts: { reject: 28 } },
+ ];
+ const box = fsmBox({
+ counts: {},
+ lanes: { "vetter-verdicts": { "ai:reject": { count: 28, prs: [] } } },
+ stateDescriptors: [
+ { key: "ai:reject", owner: "producer", act: "rework per note", kind: "blk", hist: "reject", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ }, history);
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:reject");
+ const line = tags(b, "polyline")[0];
+ assert(line, "the series draws");
+ const ys = line.getAttribute("points").split(" ").map((p) => Number(p.split(",")[1]));
+ assert(
+ ys.length === 3 && ys.every((y) => y === ys[0]),
+ `an empty histFold reads the bare key — flat, no Map fold: ${JSON.stringify(ys)}`,
+ );
+});
+
+// CONSERVATION, the invariant that would have made the blocked-on drift scream instead of
+// hiding 17 PRs: every lanes cell and every counted top-level array is claimed by exactly
+// one descriptor, or the panel says so loudly.
+Deno.test("pipeline FSM: a lane cell no descriptor claims surfaces as a loud defect, never silence", () => {
+ const hq = descHq();
+ // The tool re-laned a state but its descriptor list missed the old cell: 17 real PRs
+ // sit in occupancy nothing renders.
+ hq.lanes["producer-blocked"] = { "ai:blocked-on": { count: 17, prs: fcItems("stranded", 17) } };
+ const box = fsmBox(hq);
+ const bands = collect(box, "fsm-defect");
+ assert(bands.length === 1, `one defect band renders: ${bands.length}`);
+ const text = textOf(bands[0]);
+ assert(
+ text.includes('lanes["producer-blocked"]["ai:blocked-on"]'),
+ `it names the unclaimed cell: ${text}`,
+ );
+ assert(text.includes("17"), `it names the held inventory: ${text}`);
+ assert(text.includes("renders nowhere"), `it says the failure in words: ${text}`);
+ assert(
+ collect(bands[0], "fd-li").length === 1,
+ `exactly the one unclaimed key is reported: ${collect(bands[0], "fd-li").length}`,
+ );
+ // The band ADDS to the machine — every claimed state still renders beside it.
+ assert(
+ collect(box, "fsm-state").length === 5,
+ `the claimed machine still renders: ${collect(box, "fsm-state").length}`,
+ );
+});
+
+Deno.test("pipeline FSM: a counts key no descriptor claims is a defect; a bare top-level array is not occupancy", () => {
+ const hq = descHq();
+ // Drop the leak descriptor: counts.leaks (and with it the leaks array) goes unclaimed.
+ hq.stateDescriptors = hq.stateDescriptors.filter((d) => d.key !== "leak");
+ // A top-level array with no counts entry is not occupancy the page knows how to count
+ // — it must not be reported. Neither may the descriptor array itself ever be.
+ hq.sideChannel = [{ repo: "o/x", number: 1 }];
+ const box = fsmBox(hq);
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the defect band renders");
+ const rows = collect(band, "fd-li").map((r) => r.textContent);
+ assert(rows.length === 1, `exactly the leaks row is reported: ${JSON.stringify(rows)}`);
+ assert(
+ rows[0].includes("leaks") && rows[0].includes("2 held") && rows[0].includes("renders nowhere"),
+ `it names the array and its held inventory: ${rows[0]}`,
+ );
+});
+
+// Two DISTINCT descriptors reading one counts key: the same inventory wired into two
+// series, so it renders twice. Distinct keys are what makes this the double-claim defect
+// rather than the duplicate-key one — a lane claim id embeds the claiming state's own
+// key, so two descriptors can only collide on a lane id by REPEATING a key, which the
+// duplicate guard catches first and more specifically. The counts side has no such
+// structural protection: the id is the counts key alone.
+Deno.test("pipeline FSM: occupancy claimed by TWO descriptors is a defect too — exactly one claims each key", () => {
+ const hq = descHq();
+ hq.stateDescriptors.push({
+ key: "ai:ready-mirror",
+ owner: "producer",
+ act: "merge again",
+ kind: "flow",
+ // The SAME series `ai:ready` already draws.
+ hist: "ready",
+ histFold: [],
+ occupancy: { lane: "vetter-verdicts" },
+ });
+ const box = fsmBox(hq);
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "a double claim surfaces");
+ const text = textOf(band);
+ assert(
+ text.includes('counts["ready"]') && text.includes("2 descriptors") && text.includes("renders 2 times"),
+ `it names the key and says the inventory double-renders: ${text}`,
+ );
+ assert(
+ !text.includes("repeats the key"),
+ `distinct keys read as a double CLAIM, not a duplicate key: ${text}`,
+ );
+});
+
+Deno.test("pipeline FSM: a malformed descriptor renders no state but is reported — and takes no good descriptor with it", () => {
+ const box = fsmBox({
+ counts: { uncoveredIssues: 4 },
+ lanes: {},
+ uncoveredIssues: fcItems("uncovered", 4),
+ stateDescriptors: [
+ "junk",
+ { key: "no-occupancy", owner: "human", act: "x", kind: "blk", occupancy: {} },
+ { key: "bad-owner", owner: "misc", act: "x", kind: "blk", occupancy: { lane: "vet-lifecycle" } },
+ {
+ key: "uncoveredIssues",
+ owner: "producer",
+ act: "open a PR",
+ kind: "flow",
+ hist: "uncoveredIssues",
+ histFold: [],
+ occupancy: { counts: "uncoveredIssues", items: "uncoveredIssues", itemsAreIssues: true },
+ label: "untouched (no PR)",
+ },
+ ],
+ });
+ const keys = box.querySelectorAll("[data-t]").map((b) => b.dataset.t);
+ assert(
+ JSON.stringify(keys) === JSON.stringify(["uncoveredIssues"]),
+ `only the well-formed descriptor renders a state: ${JSON.stringify(keys)}`,
+ );
+ assert(
+ collect(box, "sc")[0].textContent === "4",
+ `and it renders its real count: ${collect(box, "sc")[0].textContent}`,
+ );
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "malformed descriptors surface in the defect band");
+ const text = textOf(band);
+ for (const i of [0, 1, 2]) {
+ assert(
+ text.includes("stateDescriptors[" + i + "]"),
+ `descriptor ${i} is reported by index: ${text}`,
+ );
+ }
+});
+
+Deno.test("pipeline FSM: a descriptor's kind is clamped to the display kinds — snapshot content cannot smuggle a class onto a box", () => {
+ const box = fsmBox({
+ counts: { uncoveredIssues: 4 },
+ lanes: {},
+ uncoveredIssues: fcItems("uncovered", 4),
+ stateDescriptors: [
+ {
+ key: "uncoveredIssues",
+ owner: "producer",
+ act: "open a PR",
+ // Not a display kind: must not land in className, where it would dim a box
+ // holding 4 real items (or fake a bottleneck / a selection).
+ kind: "zero",
+ hist: "",
+ histFold: [],
+ occupancy: { counts: "uncoveredIssues", items: "uncoveredIssues", itemsAreIssues: true },
+ },
+ ],
+ });
+ const b = collect(box, "fsm-state")[0];
+ assert(b, "the box renders, with the neutral default border");
+ const classes = b.className.split(" ");
+ for (const cls of ["zero", "rising", "sel", "lead"]) {
+ assert(!classes.includes(cls), `unknown kind never lands as .${cls}: ${b.className}`);
+ }
+});
+
+// The snapshot is untrusted field by field: a lane cell that EXISTS but carries a junk or
+// missing `count` renders 0 (dimmed), never NaN — the same coercion the sparse-absent cell
+// gets, asserted separately because Number(null) is 0 while Number(undefined) is NaN, so
+// only this shape catches a dropped `|| 0`.
+Deno.test("pipeline FSM: a descriptor lane cell with a junk count renders 0, never NaN", () => {
+ const box = fsmBox({
+ counts: {},
+ lanes: { "vetter-verdicts": { "ai:ready": { count: "junk", prs: [] } } },
+ stateDescriptors: [
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ const b = collect(box, "fsm-state")[0];
+ assert(b, "the box renders");
+ assert(
+ collect(b, "sc")[0].textContent === "0",
+ `a junk count coerces to 0: ${collect(b, "sc")[0].textContent}`,
+ );
+ assert(
+ b.className.split(" ").includes("zero"),
+ `and the box dims like any zero state: ${b.className}`,
+ );
+});
+
+// The occupancy pair exists because the two names CAN disagree (the leak state's key and
+// array already do): a descriptor claims both its counts key and its items array, so a
+// conserved snapshot with a differently-named items array raises no spurious defect.
+Deno.test("pipeline FSM: a descriptor claims BOTH its counts key and a differently-named items array", () => {
+ const box = fsmBox({
+ counts: { leakCount: 2 },
+ lanes: {},
+ leakItems: fcItems("leak", 2),
+ stateDescriptors: [
+ { key: "leak", owner: "human", act: "model it", kind: "blk", hist: "", histFold: [], occupancy: { counts: "leakCount", items: "leakItems" } },
+ ],
+ });
+ assert(
+ collect(box, "fsm-defect").length === 0,
+ "the items array is claimed too — no spurious defect",
+ );
+ assert(
+ collect(box, "sc")[0].textContent === "2",
+ "the count reads from counts.",
+ );
+ const detail = box.querySelectorAll("#fsmdetail")[0];
+ box.querySelectorAll("[data-t]")[0].click();
+ assert(
+ textOf(detail).includes("leak 1"),
+ "the click lists from the named items array",
+ );
+});
+
+// The retired-residue contract (issue-pr-cron#130, 2026-08-06 clarification): a retired
+// state's descriptor is EMITTED ONLY WHILE its occupancy is nonzero, so the consumer
+// renders every emitted descriptor and carries NO retired-detection of its own — the
+// residue is a state like any other while it lasts, and it leaves the shape when the
+// tool stops emitting it (together with its cell and counts key).
+Deno.test("pipeline FSM: a residue descriptor renders while emitted; drained, its absence renders nothing and nothing screams", () => {
+ const withResidue = {
+ counts: { blockedInfra: 2, ready: 1 },
+ lanes: {
+ "producer-blocked": { "ai:blocked-infra": { count: 2, prs: fcItems("residue", 2) } },
+ "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } },
+ },
+ stateDescriptors: [
+ { key: "ai:blocked-infra", owner: "producer", act: "strip the label", kind: "blk", hist: "blockedInfra", histFold: [], occupancy: { lane: "producer-blocked" }, label: "ai:blocked-infra (retired #108)" },
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ };
+ const box = fsmBox(withResidue);
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-infra");
+ assert(b, "the residue renders while the tool emits its descriptor");
+ assert(
+ collect(b, "sc")[0].textContent === "2",
+ `it carries its cell's count: ${collect(b, "sc")[0].textContent}`,
+ );
+ assert(
+ collect(b, "sk")[0].textContent === "ai:blocked-infra (retired #108)",
+ `the retired suffix rides the emitted label: ${collect(b, "sk")[0].textContent}`,
+ );
+ assert(collect(box, "fsm-defect").length === 0, "a claimed residue is conserved");
+
+ // Drained: the tool drops the descriptor, the cell and the counts key TOGETHER.
+ const drained = {
+ counts: { ready: 1 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } } },
+ stateDescriptors: [
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ };
+ const box2 = fsmBox(drained);
+ assert(
+ !collect(box2, "fsm-state").some((x) => x.dataset.t === "ai:blocked-infra"),
+ "no box for the drained retired state — no permanent dimmed residue",
+ );
+ assert(
+ collect(box2, "fsm-state").some((x) => x.dataset.t === "ai:ready"),
+ "the live machine still renders",
+ );
+ assert(collect(box2, "fsm-defect").length === 0, "and nothing screams: nothing unclaimed remains");
+});
+
+// The counts-side scoping the ratified contract froze: exactly four named keys may go
+// unclaimed — the non-state metrics (totalProducerPrs, openIssues, archivedRepoPrs) and
+// the legacy duplicate of claimed inventory (closeCandidateIssues). Anything else is
+// state-shaped occupancy rendering nowhere.
+Deno.test("pipeline FSM: the frozen-legacy counts keys never defect; any other unclaimed counts key does", () => {
+ const conserved = () => ({
+ counts: { ready: 2, totalProducerPrs: 145, openIssues: 767, archivedRepoPrs: 4, closeCandidateIssues: 0 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 2, prs: [] } } },
+ closeCandidateIssues: [],
+ archivedRepoPrs: fcItems("archived", 4),
+ stateDescriptors: [
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ assert(
+ collect(fsmBox(conserved()), "fsm-defect").length === 0,
+ "the four frozen-legacy keys are the whole exception — no defect band",
+ );
+ const drifted = conserved();
+ drifted.counts.blockedOn = 17;
+ const band = collect(fsmBox(drifted), "fsm-defect")[0];
+ assert(band, "an unclaimed state-shaped counts key surfaces");
+ const text = textOf(band);
+ assert(
+ text.includes("blockedOn") && text.includes("17 held") && text.includes("renders nowhere"),
+ `it names the key and its held inventory: ${text}`,
+ );
+});
+
+// The same class one level down: a lane cell carries its own two numbers — the `count` it
+// STATES and the `prs` it HOLDS. A claimed cell leaves the sweep early (n === 1), so a
+// broken or absent count over a populated list would render 0 while the click expanded
+// onto real PRs, in silence.
+Deno.test("pipeline FSM: a lane cell whose count disagrees with its prs is reported, and the box renders the larger", () => {
+ for (const [what, count] of [["junk count", "many"], ["absent count", undefined]]) {
+ const cell = { prs: fcItems("stranded", 17) };
+ if (count !== undefined) cell.count = count;
+ const box = fsmBox({
+ counts: {},
+ lanes: { "vet-lifecycle": { "ai:blocked-on": cell } },
+ stateDescriptors: [
+ { key: "ai:blocked-on", owner: "vetter", act: "clears when deps merge/close", kind: "blk", hist: "", histFold: [], occupancy: { lane: "vet-lifecycle" } },
+ ],
+ });
+ const b = collect(box, "fsm-state").find((x) => x.dataset.t === "ai:blocked-on");
+ assert(b, `the box renders (${what})`);
+ // The box shows the larger, so the list cannot hide inventory behind a broken count.
+ assert(
+ collect(b, "sc")[0].textContent === "17",
+ `the box renders the PRs it holds, not 0 (${what}): ${collect(b, "sc")[0].textContent}`,
+ );
+ // …and the count-vs-rows invariant holds: the number on the box is the number of rows.
+ const detail = box.querySelectorAll("#fsmdetail")[0];
+ b.click();
+ assert(
+ collect(detail, "li").length === 17,
+ `the box's number equals the rows it expands to (${what}): ${collect(detail, "li").length}`,
+ );
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, `the disagreement is reported (${what})`);
+ const text = textOf(band);
+ assert(
+ text.includes("states count 0") && text.includes("carries 17 PRs") && text.includes("renders 17"),
+ `it names both numbers and which was rendered (${what}): ${text}`,
+ );
+ }
+});
+
+Deno.test("pipeline FSM: a lane cell whose count agrees with its prs reports nothing", () => {
+ const box = fsmBox({
+ counts: {},
+ lanes: { "vet-lifecycle": { "ai:blocked-on": { count: 3, prs: fcItems("blocked", 3) } } },
+ stateDescriptors: [
+ { key: "ai:blocked-on", owner: "vetter", act: "clears when deps merge/close", kind: "blk", hist: "", histFold: [], occupancy: { lane: "vet-lifecycle" } },
+ ],
+ });
+ assert(collect(box, "fsm-defect").length === 0, "an agreeing cell draws no band");
+ const b = collect(box, "fsm-state")[0];
+ assert(collect(b, "sc")[0].textContent === "3", "and renders its stated count");
+});
+
+// The page holds TWO numbers for a lane state — the lane cell it RENDERS and the `counts`
+// key its SERIES is drawn from — and the tool computes them two ways
+// (issue-pr-cron#228). Every claim check passes when they disagree (each key is claimed
+// exactly once), so only a comparison catches it: the box would draw one number under a
+// sparkline sitting at the other, silently. The live witness the moment the producer side
+// lands: `counts.ready` is 23 off the label bucket while the `vetter-verdicts` cell is
+// ABSENT (all 23 are un-vetted at head), so the box renders 0, dimmed, under a line at 23.
+Deno.test("pipeline FSM: a lane state whose counts mirror contradicts its rendered cell is a loud defect naming both numbers", () => {
+ const box = fsmBox({
+ // The label bucket says 23; classify_lane routed all of them to un-vetted, so the
+ // vetter-verdicts cell for ai:ready is absent entirely.
+ counts: { ready: 23, unvetted: 24 },
+ lanes: { "vet-lifecycle": { "un-vetted": { count: 24, prs: [] } } },
+ stateDescriptors: [
+ { key: "un-vetted", owner: "vetter", act: "vet at current head", kind: "flow", hist: "unvetted", histFold: [], occupancy: { lane: "vet-lifecycle" } },
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ // The box renders its DECLARED occupancy — the cell, not the mirror.
+ const ready = box.querySelectorAll("[data-t]").find((b) => b.dataset.t === "ai:ready");
+ assert(
+ collect(ready, "sc")[0].textContent === "0",
+ `the box renders the lane cell, absent ⇒ 0: ${collect(ready, "sc")[0].textContent}`,
+ );
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the disagreement surfaces");
+ const text = textOf(band);
+ assert(text.includes('counts["ready"]') && text.includes("23"), `it names the mirror and its number: ${text}`);
+ assert(
+ text.includes('lanes["vetter-verdicts"]["ai:ready"]') && text.includes("holds 0"),
+ `it names the rendered occupancy and its number: ${text}`,
+ );
+ assert(text.includes("renders 0"), `it says which number is on screen: ${text}`);
+ assert(
+ text.includes("disagrees with itself"),
+ `it reports a snapshot contradiction, not a guess at the right number: ${text}`,
+ );
+ // The agreeing state says nothing.
+ assert(
+ !text.includes("un-vetted"),
+ `a state whose two numbers agree stays silent: ${text}`,
+ );
+ // And the claim is KEPT: no "unclaimed counts key" defect rides along.
+ assert(!text.includes("renders nowhere"), `the mirror is still claimed: ${text}`);
+});
+
+Deno.test("pipeline FSM: lane states whose counts mirror agrees draw no divergence defect", () => {
+ // descHq's mirrors agree with their cells (ready 5, blockedOn 3), and ai:design has a
+ // sparse-absent cell with no mirror at all — neither shape may report.
+ assert(
+ collect(fsmBox(descHq()), "fsm-defect").length === 0,
+ "a self-consistent snapshot stays silent",
+ );
+});
+
+// The frozen-legacy exception licenses a key going UNCLAIMED; it never licenses the same
+// inventory being wired twice. Two descriptors both drawing `openIssues` would render the
+// open-issue population as two state series.
+Deno.test("pipeline FSM: a frozen-legacy counts key is exempt from unclaimed, never from a DOUBLE claim", () => {
+ const box = fsmBox({
+ counts: { openIssues: 767, ready: 0 },
+ lanes: {},
+ stateDescriptors: [
+ { key: "a", owner: "producer", act: "x", kind: "flow", hist: "openIssues", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ { key: "b", owner: "vetter", act: "y", kind: "flow", hist: "openIssues", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the double claim on a frozen-legacy key surfaces");
+ const text = textOf(band);
+ assert(
+ text.includes('counts["openIssues"]') && text.includes("2 descriptors"),
+ `it names the key and the double claim: ${text}`,
+ );
+});
+
+// Two descriptors naming the same top-level array: distinct keys, possibly distinct
+// counts keys, and yet both boxes open onto ONE list — #141 by another route, invisible
+// to the lane and counts checks.
+Deno.test("pipeline FSM: two descriptors naming the same items array is a defect", () => {
+ const box = fsmBox({
+ counts: { leaks: 2, alsoLeaks: 2 },
+ lanes: {},
+ leaks: fcItems("leak", 2),
+ stateDescriptors: [
+ { key: "leak", owner: "human", act: "model it", kind: "blk", hist: "leaks", histFold: [], occupancy: { counts: "leaks", items: "leaks" } },
+ { key: "leak-copy", owner: "producer", act: "model it", kind: "blk", hist: "alsoLeaks", histFold: [], occupancy: { counts: "alsoLeaks", items: "leaks" } },
+ ],
+ });
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the shared list surfaces");
+ const text = textOf(band);
+ assert(
+ text.includes('"leaks" is named by 2 descriptors') && text.includes("open onto the same list"),
+ `it names the array and the collision: ${text}`,
+ );
+ // One array named once is not a defect.
+ assert(
+ collect(fsmBox(descHq()), "fsm-defect").length === 0,
+ "distinct arrays stay silent",
+ );
+});
+
+// The zero-held carve-out must not swallow a cell that HOLDS PRs behind a dropped or
+// non-numeric count — the one place the sweep has the list in hand to check.
+Deno.test("pipeline FSM: an unclaimed cell holding PRs with no usable count still screams", () => {
+ for (const [what, cell] of [
+ ["absent count", { prs: fcItems("stranded", 17) }],
+ ["junk count", { count: "many", prs: fcItems("stranded", 17) }],
+ ]) {
+ const box = fsmBox({
+ counts: { ready: 1 },
+ lanes: {
+ "vetter-verdicts": { "ai:ready": { count: 1, prs: [] } },
+ "producer-blocked": { "ai:blocked-on": cell },
+ },
+ stateDescriptors: [
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, `the stranded cell surfaces (${what})`);
+ assert(
+ textOf(band).includes("17 held"),
+ `the list it carries is what it holds (${what}): ${textOf(band)}`,
+ );
+ }
+});
+
+// Prototype-shaped strings off the untrusted snapshot must be ordinary data everywhere
+// they are used as a lookup or a set member. The code is written to be immune (Map/Set,
+// hasOwnProperty.call, Array.isArray); this pins it so a refactor to plain objects —
+// where `__proto__` and `constructor` are not ordinary keys — fails loudly instead of
+// silently mis-claiming, mis-rendering or throwing.
+Deno.test("pipeline FSM: prototype-shaped keys, counts, items and lane names are ordinary data", () => {
+ // Built through JSON.parse — the only way to get a genuine OWN `__proto__` key (an
+ // object literal's `__proto__:` sets the prototype instead), and exactly how the page
+ // receives the snapshot: `fetch(...).then((r) => r.json())`.
+ const box = fsmBox(JSON.parse(JSON.stringify({
+ counts: { PROTO: 3, constructor: 2, toString: 0 },
+ lanes: { constructor: { PROTO: { count: 4, prs: fcItems("proto", 4) } } },
+ PROTO: fcItems("protoitems", 3),
+ stateDescriptors: [
+ // A lane-sourced state whose key AND lane are prototype-shaped.
+ { key: "PROTO", owner: "vetter", act: "vet", kind: "flow", hist: "", histFold: [], occupancy: { lane: "constructor" } },
+ // A counts-sourced state whose counts key and items array are prototype-shaped.
+ { key: "protoCounts", owner: "producer", act: "open a PR", kind: "flow", hist: "constructor", histFold: [], occupancy: { counts: "constructor", items: "PROTO", itemsAreIssues: true } },
+ ],
+ }).replace(/PROTO/g, "__proto__")));
+ const keys = box.querySelectorAll("[data-t]").map((b) => b.dataset.t);
+ assert(
+ JSON.stringify(keys) === JSON.stringify(["protoCounts", "__proto__"]),
+ `both states render, keyed by their own strings: ${JSON.stringify(keys)}`,
+ );
+ const byT = (k) => box.querySelectorAll("[data-t]").find((b) => b.dataset.t === k);
+ assert(
+ collect(byT("__proto__"), "sc")[0].textContent === "4",
+ `a prototype-shaped lane+key resolves its own cell: ${collect(byT("__proto__"), "sc")[0].textContent}`,
+ );
+ assert(
+ collect(byT("protoCounts"), "sc")[0].textContent === "2",
+ `a prototype-shaped counts key resolves its own number: ${collect(byT("protoCounts"), "sc")[0].textContent}`,
+ );
+ // Each opens onto its OWN list — registration is keyed by these strings too.
+ const detail = box.querySelectorAll("#fsmdetail")[0];
+ byT("__proto__").click();
+ assert(textOf(detail).includes("proto 1"), `the lane cell's own list: ${textOf(detail)}`);
+ byT("__proto__").click();
+ byT("protoCounts").click();
+ assert(textOf(detail).includes("protoitems 1"), `the named array's own list: ${textOf(detail)}`);
+ // The sweep treats them as data: `counts.__proto__` (3 held) is claimed by nobody and
+ // reports; the claimed `constructor` does not; `toString` holds 0 and is skipped.
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the unclaimed prototype-shaped counts key reports");
+ const rows = collect(band, "fd-li").map((r) => r.textContent);
+ assert(
+ rows.some((r) => r.includes('counts["__proto__"]') && r.includes("3 held")),
+ `it names the unclaimed prototype-shaped key: ${JSON.stringify(rows)}`,
+ );
+ assert(
+ !rows.some((r) => r.includes('counts["constructor"]')),
+ `a claimed prototype-shaped key is not reported: ${JSON.stringify(rows)}`,
+ );
+ assert(
+ !rows.some((r) => r.includes('counts["toString"]')),
+ `a zero-held prototype-shaped key is skipped like any other: ${JSON.stringify(rows)}`,
+ );
+});
+
+// A state key is the panel's IDENTITY for a state — the box's `data-t` and the
+// register/itemsFor map key the click-to-expand reads — so two descriptors sharing one
+// key let the second's registration OVERWRITE the first's, and the first box opens onto
+// the second's list. #141's exact failure, and neither claim check sees it: with
+// different occupancy sources the two claims are different ids, so both read as singly
+// claimed. The repeat therefore renders no state and is reported, first occurrence wins.
+Deno.test("pipeline FSM: a repeated descriptor key renders one box and is reported, with the loser's occupancy named unclaimed", () => {
+ const box = fsmBox({
+ counts: { ready: 5, readyDupe: 9 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 5, prs: fcItems("first", 5) } } },
+ readyDupe: fcItems("second", 9),
+ stateDescriptors: [
+ // FIRST wins: array order is render order.
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ // Same key, a DIFFERENT occupancy source — so the two claims are different ids and
+ // the sweep alone would call both singly claimed.
+ { key: "ai:ready", owner: "producer", act: "open a PR", kind: "flow", hist: "readyDupe", histFold: [], occupancy: { counts: "readyDupe", items: "readyDupe", itemsAreIssues: true } },
+ ],
+ });
+ const boxes = box.querySelectorAll("[data-t]").filter((b) => b.dataset.t === "ai:ready");
+ assert(boxes.length === 1, `exactly one box carries the key: ${boxes.length}`);
+ assert(
+ collect(boxes[0], "sc")[0].textContent === "5",
+ `the FIRST descriptor's occupancy renders: ${collect(boxes[0], "sc")[0].textContent}`,
+ );
+ // Its click-through is its OWN list, not the repeat's — the overwrite this guard exists
+ // to prevent.
+ const detail = box.querySelectorAll("#fsmdetail")[0];
+ boxes[0].click();
+ assert(collect(detail, "li").length === 5, `it opens onto its own 5 rows: ${collect(detail, "li").length}`);
+ assert(textOf(detail).includes("first 1"), `and onto its own items: ${textOf(detail)}`);
+ assert(!textOf(detail).includes("second 1"), "never the repeat's list");
+
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the repeat is reported");
+ const rows = collect(band, "fd-li").map((r) => r.textContent);
+ assert(
+ rows.some((r) => r.includes("stateDescriptors[1]") && r.includes('repeats the key "ai:ready"')),
+ `named by index and key: ${JSON.stringify(rows)}`,
+ );
+ // Its claim was never registered, so the occupancy it meant to claim reads unclaimed.
+ assert(
+ rows.some((r) => r.includes("readyDupe") && r.includes("9 held") && r.includes("renders nowhere")),
+ `the loser's occupancy is named unclaimed: ${JSON.stringify(rows)}`,
+ );
+});
+
+Deno.test("pipeline FSM: all-distinct descriptor keys draw no duplicate-key defect", () => {
+ assert(
+ collect(fsmBox(descHq()), "fsm-defect").length === 0,
+ "a conserved, distinctly-keyed snapshot still renders no band",
+ );
+});
+
+// A descriptor also READS its `histFold` keys — that is where its series draws the
+// absorbed retirees' samples from — so those counts keys are claimed by it. The live
+// shape that makes this load-bearing: `ai:reject` folds `humanReject`/`relink` while the
+// residue rows carry NO `hist` of their own (the successor draws the past, so the same
+// history is not rendered twice). Without the fold claim, a nonzero absorbed-retiree
+// count paints a FALSE defect on a healthy snapshot.
+Deno.test("pipeline FSM: a folded retired counts key is claimed by the folding descriptor, nonzero and all", () => {
+ const hq = {
+ counts: { reject: 66, humanReject: 4, relink: 0 },
+ lanes: {
+ "vetter-verdicts": { "ai:reject": { count: 66, prs: [] } },
+ "human-decisions": { "human:reject": { count: 4, prs: fcItems("hreject", 4) } },
+ },
+ stateDescriptors: [
+ // The live row folds both retired keys and owns the continuous series.
+ { key: "ai:reject", owner: "producer", act: "rework per note", kind: "blk", hist: "reject", histFold: ["humanReject", "relink"], occupancy: { lane: "vetter-verdicts" } },
+ // The residue row: its LANE cell is its occupancy; it carries no hist, because the
+ // successor above already draws its past.
+ { key: "human:reject", owner: "human", act: "strip the label", kind: "blk", hist: "", histFold: [], occupancy: { lane: "human-decisions" }, label: "human:reject (retired #133)" },
+ ],
+ };
+ const box = fsmBox(hq);
+ assert(
+ collect(box, "fsm-defect").length === 0,
+ `a folded nonzero retired key is claimed, not a defect: ${textOf(collect(box, "fsm-defect")[0] || makeEl("div"))}`,
+ );
+ // Both rows render, each off its own lane cell.
+ const byT = (k) => box.querySelectorAll("[data-t]").find((b) => b.dataset.t === k);
+ assert(collect(byT("ai:reject"), "sc")[0].textContent === "66", "the live row reads its cell");
+ assert(collect(byT("human:reject"), "sc")[0].textContent === "4", "the residue row reads its cell");
+
+ // And the fold is not a blanket amnesty: a retired counts key NO descriptor folds or
+ // reads still screams when it holds inventory.
+ const stranded = JSON.parse(JSON.stringify(hq));
+ stranded.counts.awaitingReVet = 7;
+ const band = collect(fsmBox(stranded), "fsm-defect")[0];
+ assert(band, "an unfolded, unclaimed retired key still surfaces");
+ assert(
+ textOf(band).includes("awaitingReVet") && textOf(band).includes("7 held"),
+ `it names the stranded key: ${textOf(band)}`,
+ );
+});
+
+// The fold claim must not weaken the double-claim direction: a key claimed BOTH as one
+// descriptor's fold and as another's live hist is the same inventory wired twice — the
+// producer side is held to fold keys being retired (never a live row's hist), so this
+// reads as the structural defect it is.
+Deno.test("pipeline FSM: a counts key claimed as both a fold and another descriptor's hist is still a double claim", () => {
+ const box = fsmBox({
+ counts: { reject: 66, humanReject: 4 },
+ lanes: {
+ "vetter-verdicts": { "ai:reject": { count: 66, prs: [] } },
+ "human-decisions": { "human:reject": { count: 4, prs: [] } },
+ },
+ stateDescriptors: [
+ { key: "ai:reject", owner: "producer", act: "rework per note", kind: "blk", hist: "reject", histFold: ["humanReject"], occupancy: { lane: "vetter-verdicts" } },
+ // Illegal per the contract: the residue row draws the SAME history the fold above
+ // already absorbed, so humanReject's inventory would render twice.
+ { key: "human:reject", owner: "human", act: "strip the label", kind: "blk", hist: "humanReject", histFold: [], occupancy: { lane: "human-decisions" } },
+ ],
+ });
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the double claim surfaces");
+ const text = textOf(band);
+ assert(
+ text.includes("humanReject") && text.includes("2 descriptors") && text.includes("renders 2 times"),
+ `it names the key and says it renders twice: ${text}`,
+ );
+});
+
+// Zero inventory renders nowhere VACUOUSLY: the live tool still emits drained retired
+// counts keys at 0 (kept-while-nonzero governs the descriptor list, not the keys), so an
+// unclaimed key holding nothing is not hidden inventory and must not paint a permanent
+// false defect on a healthy snapshot. The same keys NONZERO are the real hazard and
+// scream; a DOUBLE claim is structural — the same inventory wired twice — and defects at
+// any count, zero included.
+Deno.test("pipeline FSM: zero-held unclaimed occupancy is vacuously conserved; nonzero screams; a zero-held double claim still defects", () => {
+ const base = () => ({
+ counts: { ready: 2, blockedInfra: 0 },
+ lanes: {
+ "vetter-verdicts": { "ai:ready": { count: 2, prs: [] } },
+ "producer-blocked": { "ai:blocked-deploy": { count: 0, prs: [] } },
+ },
+ stateDescriptors: [
+ { key: "ai:ready", owner: "human", act: "merge", kind: "flow", hist: "ready", histFold: [], occupancy: { lane: "vetter-verdicts" } },
+ ],
+ });
+ assert(
+ collect(fsmBox(base()), "fsm-defect").length === 0,
+ "a drained retired counts key and an empty unclaimed cell hide nothing — no band",
+ );
+
+ const held = base();
+ held.counts.blockedInfra = 2;
+ held.lanes["producer-blocked"]["ai:blocked-deploy"].count = 3;
+ const band = collect(fsmBox(held), "fsm-defect")[0];
+ assert(band, "the same keys nonzero surface");
+ const text = textOf(band);
+ assert(
+ text.includes("blockedInfra") && text.includes("2 held"),
+ `the nonzero drained key screams: ${text}`,
+ );
+ assert(
+ text.includes("ai:blocked-deploy") && text.includes("3 held"),
+ `the nonzero unclaimed cell screams: ${text}`,
+ );
+
+ // Two DISTINCT descriptors reading the SAME zero-held counts key — a double claim, not
+ // a duplicate key (which is its own, earlier defect).
+ const doubled = base();
+ doubled.stateDescriptors.push(
+ { key: "ai:blocked-infra", owner: "human", act: "strip the label", kind: "blk", hist: "blockedInfra", histFold: [], occupancy: { lane: "producer-blocked" } },
+ { key: "ai:blocked-infra-mirror", owner: "producer", act: "strip the label", kind: "blk", hist: "blockedInfra", histFold: [], occupancy: { lane: "producer-blocked" } },
+ );
+ const band2 = collect(fsmBox(doubled), "fsm-defect")[0];
+ assert(band2, "a double claim on a zero-held cell still surfaces");
+ assert(
+ textOf(band2).includes("2 descriptors"),
+ `it reads as the structural defect it is: ${textOf(band2)}`,
+ );
+});
+
+// A counts-source descriptor's declared keys must RESOLVE in the snapshot: a tool that
+// emits a descriptor pointing at nothing has broken its own contract, and the count/list
+// it strands must not degrade into a quiet zero.
+Deno.test("pipeline FSM: a descriptor whose counts or items source does not resolve is a loud defect", () => {
+ const box = fsmBox({
+ counts: {},
+ lanes: {},
+ stateDescriptors: [
+ { key: "uncoveredIssues", owner: "producer", act: "open a PR", kind: "flow", hist: "", histFold: [], occupancy: { counts: "uncoveredIssues", items: "uncoveredIssues", itemsAreIssues: true } },
+ ],
+ });
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "unresolved sources surface");
+ const rows = collect(band, "fd-li").map((r) => r.textContent);
+ assert(rows.length === 2, `both unresolved sources report: ${JSON.stringify(rows)}`);
+ assert(
+ rows.some((r) => r.includes('counts source "uncoveredIssues"') && r.includes("does not resolve")),
+ `the counts source is named: ${JSON.stringify(rows)}`,
+ );
+ assert(
+ rows.some((r) => r.includes('items source "uncoveredIssues"') && r.includes("does not resolve")),
+ `the items source is named: ${JSON.stringify(rows)}`,
+ );
+});
+
+// The frozen fallback: a descriptor-less snapshot renders the hand table exactly as
+// before this change — same boxes, same order — with no conservation band. Junk (a
+// non-array) is not a descriptor list and falls back the same way.
+Deno.test("pipeline FSM: a descriptor-less snapshot renders the frozen hand table, defect-free — junk stateDescriptors falls back too", () => {
+ const lanesHq = () => ({
+ counts: { leaks: 1, ready: 2, uncoveredIssues: 3, closeCandidateUnvetted: 0, closeCandidateUpheld: 0 },
+ lanes: {
+ "vetter-verdicts": {
+ "ai:ready": { count: 2, prs: [] },
+ "ai:reject": { count: 1, prs: [] },
+ },
+ },
+ leaks: fcItems("leak", 1),
+ uncoveredIssues: fcItems("uncovered", 3),
+ });
+ const frozen = (box, what) => {
+ const keys = box.querySelectorAll("[data-t]").map((b) => b.dataset.t);
+ assert(
+ JSON.stringify(keys) === JSON.stringify([
+ "ai:reject",
+ "uncoveredIssues",
+ "un-vetted",
+ "closeCandidateUnvetted",
+ "ai:blocked-on",
+ "leak",
+ "ai:ready",
+ "ai:design",
+ "human:design",
+ "closeCandidateUpheld",
+ ]),
+ `the frozen table renders whole and in order (${what}): ${JSON.stringify(keys)}`,
+ );
+ assert(
+ collect(box, "fsm-defect").length === 0,
+ `no conservation band on the fallback (${what})`,
+ );
+ };
+ frozen(fsmBox(lanesHq()), "absent");
+ frozen(fsmBox({ ...lanesHq(), stateDescriptors: "junk" }), "junk descriptors");
+});
+
+// An EMPTY stateDescriptors array is a present — broken — declaration: the machine claims
+// zero states while the snapshot holds occupancy. The hand table must not resurrect
+// (vocabulary lives in the tool), and the conservation band must scream instead.
+Deno.test("pipeline FSM: an empty stateDescriptors array screams via conservation rather than resurrecting the hand table", () => {
+ const box = fsmBox({
+ counts: { ready: 2 },
+ lanes: { "vetter-verdicts": { "ai:ready": { count: 2, prs: [] } } },
+ stateDescriptors: [],
+ });
+ assert(
+ collect(box, "fsm-state").length === 0,
+ `no hand-table box resurrects: ${collect(box, "fsm-state").length}`,
+ );
+ const band = collect(box, "fsm-defect")[0];
+ assert(band, "the unclaimed occupancy surfaces");
+ assert(
+ textOf(band).includes("ai:ready"),
+ `it names the stranded cell: ${textOf(band)}`,
+ );
+});
+
+// The panel's existing behaviors survive descriptor mode: sparklines draw from the
+// descriptor's hist key, and the per-actor lead fallback marks the largest flat queue.
+Deno.test("pipeline FSM: descriptor boxes keep sparklines and the lead fallback", () => {
+ const now = Date.parse("2026-08-06T00:00:00Z");
+ const history = [
+ { t: now - 2 * DAY, counts: { ready: 5 } },
+ { t: now, counts: { ready: 5 } },
+ ];
+ const box = fsmBox(descHq(), history);
+ const byT = (k) => box.querySelectorAll("[data-t]").find((b) => b.dataset.t === k);
+ const ready = byT("ai:ready");
+ assert(
+ collect(ready, "fsm-spark").length === 1,
+ "the descriptor's hist key draws a sparkline",
+ );
+ // Human group: ready 5, design 0, leak 2 — nothing rising, so the largest queue leads.
+ assert(
+ ready.classList.contains("lead"),
+ "the lead fallback marks the actor's largest queue",
+ );
+ assert(
+ !byT("leak").classList.contains("lead"),
+ "and only that one",
+ );
+});
+
// ---- deployments.html: known owners ----
// renderDeployments takes (document, $, data) as its own params, so bind with no