Skip to content

pipeline dash: render the machine from emitted stateDescriptors, and drop the deleted ai:blocked-deploy from the frozen table - #166

Merged
thedavidmeister merged 14 commits into
masterfrom
2026-08-06-consume-state-descriptors
Aug 7, 2026
Merged

thedavidmeister merged 14 commits into
masterfrom
2026-08-06-consume-state-descriptors

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes #167 — the deleted ai:blocked-deploy state leaves the frozen fallback table (detail below).

Closes #164 — the consumer half of rainlanguage/issue-pr-cron#130, built to the ratified schema contract (rainlanguage/issue-pr-cron#130 (comment)) as clarified on 2026-08-06 (rainlanguage/issue-pr-cron#130 (comment)).

The descriptor half depends on rainlanguage/issue-pr-cron#130 producer PR (rainlanguage/issue-pr-cron#223) for live effect, and is a behavioural no-op until then: the live snapshot carries no stateDescriptors, so it renders the frozen fallback exactly as before. That is why this can merge first.

The second change is NOT a no-op, and is the only thing that moves on the live board: ai:blocked-deploy leaves the frozen fallback table, so the box it renders today at a permanent dimmed zero disappears (before/after below).

What

When the fetched snapshot carries stateDescriptors, the machine renders from them and from nothing else:

  • Owner grouping — each descriptor's owner (producer|vetter|human) files it under that actor's heading; descriptor order is render order within the group; an actor the tool files no state under draws no empty heading.
  • Boxeskey/label (the emitted (retired #n) residue suffix rides through), act, kind (clamped to flow|rule|blk so snapshot content can never smuggle a meaningful class like zero/rising/sel onto a box).
  • Occupancy from the declared source{lane}: count + click-through prs from that lane's cell keyed by the state key, sparse-absent ⇒ 0; {counts, items, itemsAreIssues}: count from counts.<key>, click-through from the named top-level array, the flag picking which URL path is tried first. Every box registers its item list at the one wiring point (Four FSM boxes show a count and expand to an empty list — fromCounts never populates laneItems #141's lesson), so click-to-expand, the count==rows invariant, and the missing-list defect reading all hold identically for descriptor states.
  • History — series from hist, retired-key samples folded per the descriptor's own histFold (an empty list folds nothing; the hand HIST_FOLD Map serves only the frozen fallback). Sparklines, 7d trend borders, and the per-actor lead fallback all work unchanged.
  • Retired states need no special-casing — the tool emits a residue descriptor only while its occupancy is nonzero, so this page renders what is emitted and a drained retired state simply leaves the shape.

Conservation (the invariant that would have made the blocked-on drift scream instead of hiding 17 PRs): every lanes cell must be claimed by exactly one descriptor — zero exceptions; every counts key must be claimed by exactly one descriptor through the keys it reads (occupancy counts source or hist mirror), outside the four named frozen-legacy keys (totalProducerPrs, openIssues, archivedRepoPrs — non-state metrics — and closeCandidateIssues — the legacy duplicate of claimed inventory); and a counts-source descriptor's declared counts/items keys must resolve in the snapshot. Violations (unclaimed, double-claimed, unresolved, malformed descriptors, repeated keys) render as a loud --crit defect band in words — never silently ignored. A repeated key renders no state and is reported by key and index (first occurrence wins; array order is render order), with its claim unregistered so the sweep also names the occupancy it meant to claim — the state key is the box's data-t and the register/itemsFor map key, so a repeat would otherwise let the second's registration overwrite the first's and leave a box opening onto another state's list (#141's exact failure, invisible to both claim checks because different occupancy sources produce different claim ids). Cross-check (issue-pr-cron#228): the page holds two numbers for a lane state — the lane cell it RENDERS and the counts key its SERIES is drawn from — which the tool computes two ways. Every claim check passes when they disagree, so the page compares them and reports a divergence naming both numbers and which is on screen (it does not guess which is right). A descriptor claims every counts key it READS — its occupancy counts source, its hist, and its histFold keys (that is where its series draws the absorbed retirees' samples from, so ai:reject folding humanReject/relink claims those keys while the residue rows carry no hist of their own). One deliberate carve-out: an UNCLAIMED key holding 0 is vacuously conserved (nothing is hidden) and raises no defect — the live tool still emits drained retired counts keys at 0 while kept-while-nonzero drops only the descriptors, so flagging zero-held keys would paint a permanent false defect on the first healthy descriptor snapshot; a zero-held double claim is structural and still defects — including on the frozen-legacy keys, which are exempt from unclaimed, never from a double claim. A descriptor's items array is claimed too, so two descriptors naming one array is reported rather than rendering two boxes onto one list; an unclaimed lane cell's held inventory is max(count, prs.length), so a cell carrying PRs behind a dropped count is not scored 0 and skipped; and a repeated descriptor key renders no state and is reported.

The hand-written STATES table (and HIST_FOLD, and #161 RETIRED_NO_SUCCESSOR straggler table) survive ONLY as the frozen fallback for descriptor-less snapshots — the same posture as the legacy no-lanes branch. Master is merged in through #162 AND #165, so the frozen table is the post-#165 shape (vetter-owned ai:blocked-on reading vet-lifecycle with its staleLane read — a fallback-branch-only affordance; the descriptor path reads only each descriptor's declared lane, and a stale cell on a descriptor snapshot is conservation-band territory). On a descriptor snapshot a stale retired-state cell surfaces through the conservation band instead of the straggler table.

Also in this PR: the deleted ai:blocked-deploy state leaves the frozen table (#167)

ai:blocked-deploy is not a state of the FSM any more (rainlanguage/issue-pr-cron#225 deletes the label constant, the classifier arm, the counts emission, the display section, the label_meta row and the parking arm; the label definition is gone from every repo that defined it). The frozen table still listed it unconditionally, so the moment the tool stops emitting the lane cell the box renders permanently at zero under Human action, prescribing "resolve deploy" for a machine that no longer exists — the exact thing this file's own comment forbids (#145). It belongs in this PR because this PR is what demotes that table to a frozen fallback, and a descriptor-bearing snapshot never carries the deleted state at all.

  • Dropped from STATES, from the legacy no-lanes branch, and from the dual-owner rationale comment (one resolution survives there: ai:blocked-on).
  • Added to RETIRED_NO_SUCCESSOR beside ai:blocked-infra, so a snapshot still carrying a nonzero count — an old snapshot, or the label reapplied by hand — surfaces as a straggler box whose act is stripping the residue, instead of vanishing. Zero or absent renders nothing.
  • No HIST_FOLD entry. Nothing absorbed this inventory. ai:reject is where the PRs that carried the label now sit, but a relabel is not an absorption — a reject measures a verdict on the PR's code, never a fact about a deploy — so folding those samples into reject would fabricate a step in a series that never held that work. Its samples are drawn nowhere.

RETIRED_NO_SUCCESSOR's heading generalised with it: it was scoped to states "whose FSM exit was the run ending", which is ai:blocked-infra's exit but not this one (deleted outright). The property that actually governs both — and that drives both the straggler render and the absent fold — is that no surviving state absorbed the inventory, so the comment now states that property and gives each state's exit under it.

Tests

nix run .#dashboard-test: 238 passed, 0 failed — 27 descriptor tests, plus the straggler suite extended over BOTH no-successor retirees (one mechanism, not a parallel set) and a new pin that two retirees on one snapshot render as two independent boxes with their own counts and lists. nix develop -c pre-commit run --all-files (CI's static suite) green.

Visual evidence

Four renders for #167's change, shot at this head against master. The two descriptor renders further down are fixture-driven (clearly marked) and were shot at the descriptor commits; #167's commit does not touch the descriptor path, so they still hold.

Live BEFORE (master) / AFTER (this head). Both rendered from ONE frozen copy of the live snapshot served locally, so the branch is the only variable. The descriptor half is a no-op here (no stateDescriptors live); the whole visible difference is #167's: the dimmed ai:blocked-deploy 0 / RESOLVE DEPLOY box is gone, and the human group falls from two rows to one. The human total is unchanged at 25 — the box it removes held nothing, which is the point.

before (master) after (this head)
before after

FIXTURE-DRIVEN — the same snapshot with a nonzero ai:blocked-deploy residue (count 3 + its three PRs + a history, i.e. an old snapshot or the label reapplied by hand). This is the straggler behaviour, and it is why the state is not simply deleted from the page: before, it is a modeled state with a sparkline and the act "RESOLVE DEPLOY"; after, it is a residue box with the act "STRIP THE LABEL", no sparkline (no series absorbed it), rendered after the modeled states. The human total counts it either way (28), so the inventory never goes missing.

before (master) after (this head)
residue before residue after

FIXTURE-DRIVEN — the live snapshot plus PR 223's actual row table (human:design owner producer, "ai:blocked-deploy (retired #162)", ai:blocked-on vetter-owned on vet-lifecycle). The machine renders from descriptors — and the new cross-check catches the REAL disagreement that ships with it: ai:ready renders 0 (its vetter-verdicts cell is absent, all 23 being un-vetted at head) while counts.ready says 23, which is rainlanguage/issue-pr-cron#228:

fixture descriptors

FIXTURE-DRIVEN — re-lane drift on top: the descriptor list still points blocked-on at the old producer-blocked lane, so the 17 stranded PRs scream (unclaimed cell) alongside the two divergences:

fixture defect

The two descriptor-fixture renders above were shot at the descriptor commits and still hold at this head: #167's commit touches only STATES, RETIRED_NO_SUCCESSOR, the legacy no-lanes branch and their comments — none of which the descriptor path reads.

QA

  • Discriminating tests: 27 new Deno tests in test/dashboard.test.js — "a lane state whose counts mirror contradicts its rendered cell is a loud defect naming both numbers"; "lane states whose counts mirror agrees draw no divergence defect"; "a frozen-legacy counts key is exempt from unclaimed, never from a DOUBLE claim"; "two descriptors naming the same items array is a defect"; "an unclaimed cell holding PRs with no usable count still screams"; "prototype-shaped keys, counts, items and lane names are ordinary data"; "a repeated descriptor key renders one box and is reported, with the loser's occupancy named unclaimed"; "all-distinct descriptor keys draw no duplicate-key defect"; "a folded retired counts key is claimed by the folding descriptor, nonzero and all"; "a counts key claimed as both a fold and another descriptor's hist is still a double claim"; "zero-held unclaimed occupancy is vacuously conserved; nonzero screams; a zero-held double claim still defects"; "stateDescriptors drive the machine — owner grouping, act, count, and descriptor order"; "descriptor occupancy reads its declared source — lane prs and counts+items alike, itemsAreIssues picking the path"; "a descriptor's histFold folds retired-key samples into its series"; "a descriptor's empty histFold folds nothing — the hand-maintained fallback Map never applies"; "a lane cell no descriptor claims surfaces as a loud defect, never silence"; "a counts key no descriptor claims is a defect; a bare top-level array is not occupancy"; "occupancy claimed by TWO descriptors is a defect too"; "a malformed descriptor renders no state but is reported"; "a descriptor's kind is clamped to the display kinds"; "a descriptor lane cell with a junk count renders 0, never NaN"; "a descriptor claims BOTH its counts key and a differently-named items array"; "a residue descriptor renders while emitted; drained, its absence renders nothing and nothing screams"; "the frozen-legacy counts keys never defect; any other unclaimed counts key does"; "a descriptor whose counts or items source does not resolve is a loud defect"; "a descriptor-less snapshot renders the frozen hand table, defect-free — junk stateDescriptors falls back too"; "an empty stateDescriptors array screams via conservation rather than resurrecting the hand table". Each fails on base — base has no descriptor path, so every descriptor fixture renders the hand table and the exact-key/owner/count/defect assertions fail (the assertions name descriptor-only keys, orders and the .fsm-defect class, none of which exist on base).
  • Mutations applied: 48 mutants — all KILLED, zero survivors, each applied to site/pipeline.html with the full suite run and the file restored byte-identically (verified by hash and a clean git status). 16 cover pipeline dash: remove the deleted ai:blocked-deploy state (blocked-infra precedent) #167's change: RETIRED_NO_SUCCESSOR drops the ai:blocked-deploy entry → residue vanishes silently → killed; drops the ai:blocked-infra entry → killed (the sibling is still load-bearing); STATES reintroduces ai:blocked-deploy → the permanent dimmed box returns → killed by the frozen-table pin and the owner-group count; the nonzero guard inverted, and dropped, on BOTH the lanes and legacy branches (4 mutants) → killed by the zero/absent test (a dropped guard is exactly the Dashboard still renders awaiting-re-vet; the state was deleted by issue-pr-cron#128 #145 defect); HIST_FOLD gains a blockedDeploy fold, and a blockedInfra fold (2 mutants) → killed by the no-fold history test through ai:reject, the tempting target; straggler loses its legacy counts fallback → killed by the counts-only test; registers no item list, on both branches (2 mutants) → killed by the click-through; act reverted to "resolve deploy" → killed by the act pin; owner filter dropped (residue under every actor) → killed by the one-box-on-the-board assertion; count omitted from the owner total → killed by the total pins; straggler draws a sparkline from the retired series → killed by the no-spark assertions. The 32 descriptor mutants below were re-run in full at this head and all still die: each applied to site/pipeline.html, full suite run, file restored — all KILLED, zero survivors in the final table (re-run in full at this head). divergence check dropped → a state counted two ways passes in silence → killed by the divergence test; divergence check inverted → killed by 11 tests; frozen-legacy exception moved to also suppress double claims → killed by the legacy-double test; items double-claim sweep dropped → killed by the shared-array test; held ignores prs.length → killed by the PRs-behind-no-count test. duplicate-key guard dropped → repeats render twice and mis-wire silently → killed by the repeated-key test; duplicate-key last-wins (the repeat evicts the first from the render) → killed by the same test's first-wins assertions. fold claim dropped → nonzero folded retired keys paint false defects → killed by the fold-claim test; fold claim widened to blanket amnesty (claim every counts key) → stranded retired keys hidden → killed by that test's second half plus 6 others. zero-skip removed → flags zero-held unclaimed keys → killed by the vacuous-conservation test; zero-skip overbroad (if (!held)) → silences zero-held double claims → killed by its structural-defect assertion. defect-band render → if (false) → killed by the 5 conservation tests (7 failures); seriesFor(p.hist, p.histFold)seriesFor(p.hist) → killed by both histFold tests; lane count → Number(c[p.hist]) → killed by the decoy-mirror fixture (lane cell 3 vs counts 99); counts count → Number(c[p.key]) → killed by the two-name test; issue: !!occ.itemsAreIssuesfalse → killed by the /issues/ href pin; render order → alphabetical sort → killed by the order pin (fixture deliberately not in count order); owner filter → all-under-human → killed by the group pins; count = Number(cell && cell.count) → killed by the junk-count test; lane sweep / counts sweep skipped → killed by the unclaimed-cell / unclaimed-key tests; LEGACY_UNCLAIMED emptied → killed by the frozen-legacy test; hist claim dropped → killed by 5 tests (spurious defects on lane mirrors); counts / items resolution checks dropped → killed by the resolution test; malformed report dropped → killed by the index assertions; kind clamp → any string → killed by the clamp test; descriptor gate → truthy / length-gated → killed by the junk-fallback and empty-array tests; claim counter capped at 1 → killed by the double-claim test; lane register dropped → killed by the lane click-through; else if (hasLanes) → unconditional second render → killed by the exact-key pins (6 failures). Two earlier survivors were fixed by strengthening tests in place (fixture reordered out of count order; the junk-count pin added).
  • Oracle: the ratified schema contract on human-queue --json emits counts but not the state vocabulary, so consumers hand-copy it and drift silently issue-pr-cron#130 (comments 5203325549 and 5203664206) and the producer PR human-queue --json emits stateDescriptors: the machine's shape as data issue-pr-cron#223 emitted shape — descriptor fields, the occupancy one-of forms, sparse-absent ⇒ 0, histFold semantics, residue emit-while-nonzero, and the exact four-key frozen-legacy list; expected counts/orders in fixtures are hand-derived from those documents, never read back from the implementation.
  • Category check: issue asks (a) machine built from descriptors — owner grouping, box key/label/act/kind, occupancy from declared source, hist + histFold series, descriptor order as render order; (b) conservation check with loud defect surfacing via the existing defect-surfacing pattern; (c) STATES surviving only as the frozen fallback; (d) mergeable before the producer side (behavioral no-op absent descriptors). Covered: (a) the descriptor render branch + tests 1-4 and 9-12; (b) the claims/sweep/defect band + tests 5-8 and 13-14; (c) untouched fallback branches + the frozen-pin test + byte-identical screenshot pairs; (d) the Array.isArray gate + the empty/junk tests + the unchanged frozen-fallback branches.
  • Category check (pipeline dash: remove the deleted ai:blocked-deploy state (blocked-infra precedent) #167): issue asks (a) drop the state from STATES, the legacy branch and the dual-owner comment; (b) add it to RETIRED_NO_SUCCESSOR so a nonzero count surfaces as a straggler while zero/absent renders nothing; (c) NO HIST_FOLD entry; (d) tests move with the behaviour and a mutation pass proves the straggler surfacing load-bearing; (e) before/after renders. All five covered above — (d) by the 16 pipeline dash: remove the deleted ai:blocked-deploy state (blocked-infra precedent) #167 mutants and the extended straggler suite, (e) by the two render pairs.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Pipeline views now render from state descriptors, including labels, ownership, actions, history, and item links.
    • Added visible validation and conservation diagnostics for mismatched, missing, or malformed pipeline data.
    • Improved handling of sparse histories, empty states, stale lanes, and unclaimed items.
  • Bug Fixes

    • Removed retired deployment states from legacy and fallback displays.
    • Hardened state labels and lane access when data is incomplete or malformed.
    • Preserved sparklines and lead highlighting while improving pipeline accuracy.

thedavidmeister and others added 5 commits August 6, 2026 10:42
The consumer half of issue-pr-cron#130 (rain-org-health#164): a snapshot
carrying stateDescriptors IS the machine — owner grouping, per-state box
(key/label/act/kind), occupancy from each descriptor's declared source
({lane} cell with sparse-absent => 0, or {counts, items, itemsAreIssues}),
history series from hist with retired-key samples folded per the
descriptor's own histFold, descriptor order as render order. The
hand-written STATES table and HIST_FOLD survive only as the frozen
fallback for descriptor-less snapshots, the same posture as the legacy
no-lanes branch.

Conservation: every lanes cell and every counted top-level array must be
claimed by exactly one descriptor; unclaimed (and double-claimed)
occupancy renders as a loud --crit defect band in words — the invariant
that would have made the blocked-on re-lane scream instead of hiding 17
PRs. Malformed descriptors are reported the same way, never silently
dropped, and a descriptor's kind is clamped so snapshot content cannot
smuggle a meaningful class onto a box.

The live snapshot carries no stateDescriptors until the producer side of
issue-pr-cron#130 lands, and this render is a behavioral no-op on such
snapshots, so it can merge first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…state-descriptors

# Conflicts:
#	site/pipeline.html
…pancy claim

The descHq human states now carry the zero-count state BETWEEN two non-zero
ones, so a mutant that sorts boxes by size cannot pass the order pin by
coincidence; and a descriptor whose items array is named differently from
its counts key is pinned to claim BOTH, so the conservation sweep raises no
spurious defect on a conserved snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found by the adversarial mutation pass: Number(cell && cell.count) is
equivalent to the real coercion for a sparse-ABSENT cell (Number(null) is
0), so only a cell that exists with a junk or missing count discriminates
the dropped `|| 0` — and no descriptor-path test carried that shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-08-06 clarification on issue-pr-cron#130: lanes cells stay
strictly claimed (zero exceptions); on the counts side every counts key
must be claimed by exactly one descriptor through the keys it reads (its
occupancy counts source or its hist mirror), outside the four named
frozen-legacy keys — the non-state metrics totalProducerPrs / openIssues /
archivedRepoPrs and the legacy duplicate closeCandidateIssues — and a
counts-source descriptor's declared counts/items keys must resolve in the
snapshot or the band reports them. Replaces the counted-top-level-array
sweep, which the counts-key sweep subsumes.

Retired states arrive as residue descriptors emitted only while nonzero,
so the consumer keeps zero retired-detection: pinned by the new residue
pair test (present renders with its "(retired #n)" label; drained renders
nothing and nothing screams).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The pipeline dashboard now renders machine state from validated stateDescriptors. It dynamically handles ownership, occupancy, labels, kinds, history folds, and conservation defects while retaining frozen fallback behavior for descriptor-less snapshots.

Changes

Pipeline descriptor rendering

Layer / File(s) Summary
Descriptor metadata and history
site/pipeline.html, test/dashboard.test.js
Descriptor metadata controls state order, ownership, labels, actions, kinds, occupancy sources, and history folds. Tests cover canonical descriptors, retired-key aggregation, and explicit empty folds.
Validated rendering and conservation
site/pipeline.html, test/dashboard.test.js
Rendering validates descriptors and occupancy sources, builds dynamic state views, preserves sparklines and lead highlighting, and reports malformed, unresolved, unclaimed, multiply claimed, and mismatched occupancy.
Fallback and retired-state behavior
site/pipeline.html, test/dashboard.test.js
The frozen state table remains the fallback for absent or invalid descriptor lists. Tests cover retired residues, legacy keys, stale lanes, stragglers, and authoritative empty descriptor arrays.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Snapshot
  participant PipelineRenderer
  participant ConservationDefectPanel
  Snapshot->>PipelineRenderer: provide stateDescriptors and inventory
  PipelineRenderer->>PipelineRenderer: validate descriptors and occupancy claims
  PipelineRenderer->>ConservationDefectPanel: render conservation defects when claims are invalid
Loading

Possibly related issues

  • rainlanguage/rain-org-health#164 — Covers the descriptor-driven rendering, occupancy conservation, history folding, and fallback behavior implemented here.
  • rainlanguage/rain-org-health#167 — Covers removal and retired-state handling for ai:blocked-deploy.

Possibly related PRs

Suggested labels: ai:ready

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: descriptor-driven pipeline rendering and removal of the deleted fallback state.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-08-06-consume-state-descriptors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

thedavidmeister and others added 4 commits August 6, 2026 11:03
Master gained rain-org-health#165 after this branch cut: the frozen hand
table now files ai:blocked-on under the VETTER reading vet-lifecycle
(with a staleLane read of the pre-re-key producer-blocked cell — a
fallback-branch-only affordance). The frozen-fallback pin freezes
whatever master's table is, so its expected key order moves blocked-on
into the vetter group. The descriptor path is untouched: it reads only
each descriptor's declared lane, and a stale cell on a descriptor
snapshot is conservation-defect territory, never a silent fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live tool still emits drained retired counts keys at 0 (blockedInfra,
relink, humanReject) while the kept-while-nonzero contract drops only the
DESCRIPTORS, so the sweep as-was would paint a permanent false defect
band on the first healthy descriptor snapshot. An unclaimed key holding
nothing hides nothing: n === 0 with 0 held is skipped, in both the lanes
sweep and the counts sweep. A double claim is structural — the same
inventory wired twice — and stays a defect at any count, zero included.

Mutation pass re-run on the merged tree: 23/23 killed, including the two
new edges (zero-skip removed → false defects; zero-skip overbroad →
zero-held double claims silenced).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A descriptor READS its histFold keys — that is where its series draws the
absorbed retirees' samples from — so those counts keys are its claim too,
deduped alongside the occupancy counts source and hist. Without it, the
live shape paints a FALSE defect: 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), so a nonzero
humanReject would be claimed by nobody even though its lane cell IS
claimed and its history IS drawn. Masked today only because all three
retired counts sit at 0 and the zero-held skip swallows it; it fires the
moment any absorbed-retiree residue reappears.

The invariant stays honest in both directions: 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.

Suite 229 green; mutation table re-run at 25/25 killed, including the two
new edges (fold claim dropped -> false defects; fold claim widened to
blanket amnesty -> stranded retired keys hidden).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/dashboard.test.js`:
- Around line 3143-3169: Add a dashboard rendering test near the existing FSM
descriptor cases using a script-like payload for descriptor key, label, and act,
plus unclaimed lane and counts keys in the snapshot. Render through fsmBox, then
assert the payload appears in text, no img element is created, and the relevant
fsm-defect row exists and also contains the payload as text.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5283af3c-28d7-49f7-9826-937f31285af5

📥 Commits

Reviewing files that changed from the base of the PR and between 497dcfe and 7d6f1bd.

📒 Files selected for processing (2)
  • site/pipeline.html
  • test/dashboard.test.js

Comment thread test/dashboard.test.js
…rted

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: two boxes on screen, both opening the SECOND one's list, the
first box's inventory unreachable. That is #141's exact failure, and
neither claim check saw it: with different occupancy sources the two
claims are different ids, so both read as singly claimed.

A repeat now renders NO state and is reported by key and index, in the
malformed entry's wording, 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.

Two pre-existing double-claim tests manufactured their double claim by
repeating a key, which is now the more specific defect; both are rebuilt
on DISTINCT keys colliding on a counts id — the genuine remaining
double-claim path, since a lane claim id embeds the claiming state's own
key and so can only collide via a repeat.

Suite 231 green; mutation table 27/27 killed, including the two new
edges (guard dropped -> silent mis-wire; last-wins -> the repeat
overwrites the first's registration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thedavidmeister and others added 2 commits August 7, 2026 07:22
… fixes

SHOULD-FIX — the page holds TWO numbers for a lane state and never
compared them: the lane cell it RENDERS, and the counts key its SERIES is
drawn from. The tool computes those two ways (issue-pr-cron#228), so the
live witness the moment the producer lands is ai:ready rendering 0,
dimmed, under a sparkline sitting at 23 — with every claim check passing,
because each key is claimed exactly once. The page is the only place the
two meet, so it now compares them and reports a divergence naming both
numbers and which one is on screen. It does not guess which 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.

Also from review:
- the frozen-legacy exception no longer suppresses DOUBLE claims on those
  four keys — it licenses a key going unclaimed, never the same inventory
  wired twice (two descriptors both drawing openIssues would render the
  population as two state series);
- a descriptor's items array is claimed, so two descriptors naming one
  array is reported rather than rendering two boxes onto one list;
- an unclaimed lane cell's held inventory is max(count, prs.length), so a
  cell carrying 17 PRs behind a dropped or non-numeric count is no longer
  scored 0 and skipped by the zero-held carve-out;
- .fd-li and .sk wrap long unbroken tokens instead of overflowing, since
  both quote untrusted snapshot strings.

descHq's decoy counts mirrors are now consistent with their cells: a
mirror contradicting its cell is itself a reported defect, so the
wrong-source read is discriminated by the divergence test instead.
Prototype-shaped strings (__proto__, constructor) as key, counts, items
and lane name are pinned as ordinary data, built through JSON.parse (the
only way to get a genuine own __proto__ key, and how the page really
receives the snapshot).

Suite 237 green; mutation table 32/32 killed, including the five new
edges (divergence dropped/inverted, legacy exception suppressing doubles,
items double claim dropped, held ignoring prs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`ai:blocked-deploy` is not a state of the FSM any more (issue-pr-cron#225):
no label, no classifier arm, no counts emission, no parking transition. The
frozen fallback table still listed it unconditionally, so once the tool stops
emitting the lane cell the box renders permanently at zero under Human action,
prescribing "resolve deploy" for a machine that no longer exists — the exact
thing this file's own comment forbids (rain-org-health#145).

Drop it from STATES, from the legacy no-`lanes` branch and from the dual-owner
rationale, and add it to RETIRED_NO_SUCCESSOR beside `ai:blocked-infra`: a
snapshot still carrying a nonzero count (an old snapshot, or the label
reapplied by hand) surfaces as a straggler box whose act is stripping the
residue, and zero or absent draws nothing.

No HIST_FOLD entry. Nothing absorbed this inventory — `ai:reject` is where the
PRs that carried the label now sit, but a reject measures a verdict on the PR's
code, not a fact about a deploy, so folding the samples in would fabricate a
step in a series that never held that work.

The straggler tests are extended over BOTH no-successor retirees rather than
duplicated, since it is one mechanism, plus a new pin that two retirees on one
snapshot render as two independent boxes with their own lists and counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister changed the title pipeline dash: render the machine from emitted stateDescriptors pipeline dash: render the machine from emitted stateDescriptors, and drop the deleted ai:blocked-deploy from the frozen table Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@site/pipeline.html`:
- Around line 1022-1025: Update the lane cell scoring logic around laneCount to
detect when cell.count is unset or nonnumeric while cell.prs is populated,
matching the existing count-versus-hist disagreement handling; ensure this
defect is included in the misclaimed sweep. Change the related click-through
label to display the actual cell.prs list length instead of the normalized
count.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 34f448e4-93f3-4b2b-a6bd-8de3014615bb

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6f1bd and 3688989.

📒 Files selected for processing (2)
  • site/pipeline.html
  • test/dashboard.test.js

Comment thread site/pipeline.html
thedavidmeister and others added 2 commits August 7, 2026 11:41
`Number(cell.count) || 0` cannot tell a missing or non-numeric count from
a real zero, so a broken count over a populated `prs` rendered 0 while the
click expanded onto real PRs — and the sweep could not see it, because a
claimed cell leaves `misclaimed` early at n === 1. Same class as the
count-vs-hist divergence, one level down: the page holds two numbers for
one cell and is the only place they meet.

The box now renders the LARGER, so a broken count cannot hide inventory
the list proves is there and the number on the box is the number of rows
it opens onto; the disagreement is reported naming both numbers and which
was rendered. Only checked while the list is non-empty — a count over an
EMPTY list is the missing-list case detailBody already reports at the
click, not a snapshot defect.

Targeted mutants (3, all killed): the check dropped; the check widened to
fire on an empty list; the render reading the stated count instead of the
larger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tors' into 2026-08-06-consume-state-descriptors

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/dashboard.test.js`:
- Around line 3725-3771: Run deno fmt on the test block in dashboard.test.js,
ensuring the long stateDescriptors literals and surrounding test code use Deno’s
required formatting without changing test behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e973be4c-3b04-439a-a5f5-e44b2ffe905b

📥 Commits

Reviewing files that changed from the base of the PR and between 3688989 and 8cacfa3.

📒 Files selected for processing (2)
  • site/pipeline.html
  • test/dashboard.test.js

Comment thread test/dashboard.test.js
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed 8cacfa3: approve — read the diff independently across four rounds and verified the final head. The dashboard now renders the pipeline machine from the tool's emitted stateDescriptors, with the hand-written STATES table demoted to a frozen fallback, so the state vocabulary lives in one place and a state added, renamed, re-laned or retired upstream reaches the page as data. Descriptors are screened as untrusted input (kind clamped so snapshot content cannot smuggle a class onto a box, repeated keys render nothing and are reported, malformed entries reported by index). The conservation sweep reports unclaimed, double-claimed and unresolvable occupancy in a defect band that carries its meaning in words rather than colour, and the divergence check compares the two numbers the page holds for a lane state — the only place they meet — naming both and which is rendered. This round closed the last gap: a lane cell with a junk or absent count over 17 prs rendered 0 while expanding onto all 17; it now renders the larger, the box equals its rows, and the disagreement is reported. Also removes the deleted ai:blocked-deploy from the frozen table, surfacing a stale nonzero count as a straggler instead. 240 tests, mutation coverage carried across every round, 0 unresolved threads — the last was a deno fmt claim I checked against the file: deno fmt --check reports clean at this head, matching CI's denofmt hook.
Rulings-conformance: checked against CLAUDE.md and every ruling stated for this work. (1) issue 164 and issue-pr-cron#130's ratified contract with both clarifications — descriptor-driven render, retired states emitted only while occupied so no permanent dimmed boxes, every emitted lane cell claimed. (2) issue 167 — the deleted blocked-deploy box removed on the blocked-infra precedent, with no HIST_FOLD since nothing absorbed its inventory. (3) 'the prs are open, trying to get them right' — every review finding was folded into this PR rather than filed, including this last one. (4) 'can we get test coverage rather than manual verification' — the hand-checks became tests, and the NO_SUCCESSOR table made the next retirement data rather than a new test. (5) CLAUDE.md consumer-not-producer — presentation only, no pipeline state computed here. (6) CLAUDE.md untrusted-data — DOM construction throughout, no markup sinks, prototype-shaped keys pinned as ordinary data. The artifact obeys each.

@thedavidmeister
thedavidmeister merged commit 46060d1 into master Aug 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant