Skip to content

human-queue --json emits stateDescriptors: the machine's shape as data - #223

Merged
thedavidmeister merged 10 commits into
mainfrom
2026-08-06-emit-state-descriptors
Aug 7, 2026
Merged

human-queue --json emits stateDescriptors: the machine's shape as data#223
thedavidmeister merged 10 commits into
mainfrom
2026-08-06-emit-state-descriptors

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes #130 — as amended by the ratified schema contract in #130 (comment) and the two ratified clarifications in #130 (comment).

Closes #228 — the same divergence one layer down, folded in here rather than chased as a follow-up because it is the same bug in the same region (see "One population per state (#228)" below).

What this emits

human-queue --json gains a top-level stateDescriptors array — the machine's SHAPE as data, so a consumer renders what the pipeline says exists instead of hand-copying the vocabulary and drifting silently. Per state: key, owner (producer|vetter|human), act, kind (flow|rule|blk), hist, histFold, occupancy, optional label. Array order is the render order. lanes stays the sparse occupancy.

occupancy is exactly one of the contract's two forms:

  • { "lane": "<lane>" } — count+prs from that lanes cell, sparse-absent ⇒ 0;
  • { "counts": "<key>", "items": "<array>", "itemsAreIssues": <bool> } — amendment 1's four non-lane states (uncoveredIssues, leak, closeCandidateUnvetted, closeCandidateUpheld) declare where their occupancy lives at the top level. leak reads the top-level leaks array — the one carrying each leak's reason.

Clarification 1 — a retired state exists only as its residue. Live rows always emit (an empty live state stays visible — the shape is what exists). A residue row — ai:relink, ai:blocked-deploy, ai:blocked-infra, human:reject — emits only while it is OCCUPIED, so a drained retired state leaves the shape instead of renting a permanent dimmed box (rain-org-health#145's rejected outcome, the box rainlanguage/rain-org-health#162 just removed). No retired field reaches the wire; the "(retired #n)" suffix stays in label while the residue lasts.

human:design WAS live here — the row emitted Emit::Always on the reasoning that a delegated ruling refills the population in the ordinary course. #219 has since deleted the state outright (merged as #224), which is the wholesale row drop that reasoning distinguished itself from, so the row is gone rather than gated. human-decisions now holds exactly one state, the retired human:reject residue.

The gate names no key of its own — it reads the row's DECLARED occupancy, the same place it points consumers. A separately-keyed gate can measure a different population than the occupancy it gates, and then both directions are wrong. That was real, not hypothetical: counts.blockedDeploy counts the first ai:* label (ai_state_label) while the cell is classify_lane PRECEDENCE, so on two-label PRs — normal per classify_lane's own comments — ["human:design", "ai:blocked-deploy"] gave count 1 over an EMPTY cell (a dimmed box holding nothing) and ["ai:ready", "ai:blocked-deploy"] gave count 0 over a FULL cell (a real PR rendering nowhere). Reading declared occupancy makes both unconstructible. (That example is now historical twice over: #228 below made every lane row's counts key the cell itself, and #221 deleted ai:blocked-deploy outright.)

Clarification 2 — every emitted lanes cell is claimed. lanes_doc no longer emits the redundant lanes.leak cell (verified unconsumed before dropping: the tool's own review path prints leaks from the top-level array, and site/pipeline.html reads fromCounts: "leaks", items: "leaks" — no lanes.leak read exists). Every emitted lane cell is claimed by exactly one descriptor, with zero declared exceptions on the lanes side.

One table (amendment 2)

The descriptors are not a second list inside the tool. There is ONE row table, and classify_lane's own constants are spelled from it: HUMAN_DECISION_LABELS, VETTER_VERDICT_LABELS, RETIRED_HUMAN_REJECT_LABEL, RETIRED_BLOCKED_DEPLOY_LABEL, RETIRED_STATE_LABEL, and the classifier's synthesised un-vetted/leak words are all row references — so a state cannot exist for the classifier and be missing from the emitted vocabulary. The ai:close-candidate hand-off keeps no row of its own: the flag machinery inventories its subjects into the two close-candidate rows (#211 / #212), and a row for it would name occupancy that lives nowhere.

That is what the sibling deletion of human:design (#219) did when it landed: it removed the state from these same shared constants, and the descriptor dropped with it — no special-casing, and HUMAN_DECISION_LABELS went with it rather than being re-literalised to keep the tree compiling.

Owners are the machine's rulings

History folds

histFold names the keys a series draws BESIDE its own — an absorbed retiree's past, claimed by exactly one successor and by no row as its own hist, so each sample draws exactly once:

The two absorbed residue rows (human:reject, ai:relink) therefore carry no series of their own: hist is optional and omitted from the wire for them, so the successor draws the past and the same inventory never draws in two boxes (rain-org-health#162's design language for absorbed retirees). They still gate on their kept-while-nonzero counts, so a straggler stays visible as a state — Emit::WhileOccupied carries its gate key rather than reading hist, keeping the gate and the series claim independent.

humanCloseCandidate folds nowhere by design: a close ruling is human-close's decide+do with no state between (#213), so no live series measures what it measured.

One population per state (#228)

The same divergence this branch's Emit gate stopped having, one layer down. counts.<state> was computed from the label bucket (ai_state_label, the first ai:* label a PR carries) while lanes[<lane>][<state>] was computed from classify_lane precedence — so the document answered "how many PRs are in this state?" twice, with different numbers, and nothing compared them. Live on 2026-08-06: counts.ready 23 over an absent vetter-verdicts.ai:ready cell (all 23 stale at head, so un-vetted held every one), which the dashboard drew as a merge inbox of 0 beneath a sparkline plotted at 23. blockedDeploy had the same shape available to it — this branch's own Emit doc named it — and would have fired the moment a PR carried two state labels; #221 has since deleted that state entirely, so it is a hazard that expired rather than one that was survived.

Every lane row's counts key is now its cell, and by the same route the gate takes: StateOccupancy::Lane gained the counts key its cell fills, so both occupancy variants name one, and lane_counts_json writes each key through descriptor_occupancy — the accessor Emit::WhileOccupied already reads. A key, its gate and its rendered cell are one number by construction, not by three call sites agreeing. Eight keys are written this way: the four that were the label bucket (ready, design, blockedOn, blockedInfra) and the five that already read a cell through their own hand-written lane_state_count(…) call (unvetted, reject, relink, humanReject) — the hand-written calls are gone too, because a re-laned state has to carry its key with it rather than need a second edit someone remembers.

The counts.<counts> field is deliberately not on the wire: the ratified occupancy form for a lane row is {lane} and nothing else. A consumer reads the cell, which is the population; the key is the producer-side mirror, already named as hist by every row that owns a series. The kept-while-nonzero contract for the retired keys becomes structural as a side effect — every lane row emits its key at 0 rather than being dropped, and it leaves only when the row does.

No new key for the label bucket. The population is real and has a reader ("which PRs carry this label, whatever their head") but it was already emitted: the top-level states object is that census, click-through like every other array, and it has never meant anything else. A second counts.*Labelled key would restate states.<label>.length and would land as an unclaimed counts key under this branch's own conservation sweep. The two differ exactly where the classifier routes a PR past its own label — a stale-at-head ai:ready PR is in states["ai:ready"] and in the un-vetted cell, because it carries the word and is not in the state.

The daily human-readable review had the same defect and is fixed with it. Four of its sections (ai:ready, ai:design, and the two blocked residues) printed the label bucket, so the tool disagreed with the --json the same run emits — it listed those 23 PRs under MERGE — ai:ready while its own document put every one in un-vetted. The sections are now review_lane_sections(lanes), a pure function handed the cells and nothing else: there is no bucket in scope to print, which makes the regression unconstructible rather than merely corrected. Each section names its descriptor row rather than a (lane, state) pair, so the section, the counts key and the emitted descriptor read one declaration — and a residue section is gated by that row's own Emit, so a drained retiree leaves the review exactly as it leaves the emitted shape.

One cosmetic consequence, stated because the snapshot is committed: the crate serialises with serde_json's preserve_order, so counts key ORDER follows insertion — now the lane rows in table order, then the non-state rollups. No key is added or dropped and no value changes because of it, but the next refresh-human-queue tick rewrites human-queue.json with the keys in the new order, once.

The step this puts in human-queue-history.jsonl

queue-history-line copies counts verbatim, so a series means whatever its key meant on the day it was sampled. Four keys change meaning at this commit and the past is not rewritten — those lines are true measurements of the machine as it then was, which is the same rule histFold already applies to a renamed series. Documented in lane_counts_json's own rationale, on queue_history_line (the one writer of every line in that file), and in the README section the series is described in, so the discontinuity is stated rather than left as a silent step in the chart.

Only ready steps visibly — 23 → 0 on the 2026-08-06 snapshot — because ai:ready is the only label classify_lane splits on head drift. The inventory did not move (un-vetted already held those PRs, and still reads 29 against the same snapshot); the measurement did. The other three have no such split, so they can differ only on a PR carrying two state labels, which is off-protocol and empirically absent: on that snapshot design 24, blockedOn 17 and blockedInfra 0 are already equal to their cells and do not move at all. blockedDeploy is a separate lineage: it does not change meaning, it ends#221 deleted the state, so the key stops being emitted and its past stands as the record of a state the machine no longer has.

Conservation tests (amendment 2, producer side)

  • every_classifier_state_has_exactly_one_descriptor — a sweep built from the classifier's own label constants (not a hand list) asserts every reachable (lane, state) has exactly one descriptor claiming it, the close-candidate hand-off is inventoried by both cc descriptors, and every lane-occupancy descriptor names a classifier-reachable cell.
  • every_emitted_lane_cell_is_claimed_by_an_emitted_descriptor — conservation on the wire: stateDescriptors AS EMITTED (through the Emit filter, not the table behind it) against the emitted lanes, over a fixture that INCLUDES multi-label PRs — every ordered pair of state labels, the only inputs where a label-bucket key and a precedence winner disagree. Both directions: no emitted cell unclaimed, and no residue row emitted over an empty cell.
  • every_descriptor_occupancy_source_resolves_in_the_emitted_document — every declared hist, counts, items and gate key resolves in a document built by the same human_queue_doc that ships; a fold key must not be any row's claimed hist (an ownership transfer, not a share) and must fold into exactly one series; every counts key is claimed by exactly one descriptor — as a live series or as a folded past — or is a named non-state rollup (closeCandidateIssues, totalProducerPrs, archivedRepoPrs, openIssues); and the emission is wired and gated by the document's own counts.
  • state_descriptors_emit_the_ratified_shape — the contract pinned verbatim at full occupancy (all 14 rows), order included.
  • a_residue_row_emits_only_while_occupied — the live set pinned at zero occupancy (every live row visible while empty); a nonzero producer-blocked.ai:blocked-infra CELL returns exactly that row at its table position with its "(retired Producer should exit and log an error when infra is down — and blocked-infra should not be a state #108)" label; and each ABSORBED residue row (human:reject, ai:relink), while occupied, emits with no hist of its own while ai:reject carries that key in its histFold.
  • every_lane_states_counts_key_is_its_lane_cell (human-queue --json: counts.* (label bucket) and lanes cells (classifier) measure the same state differently — counts.ready 23 vs an absent lane cell #228) — the agreement, swept by ITERATING the table rather than naming states, over the same multi-label fixture: for every lane row, counts.<its declared key> equals the cell it declares as its occupancy. Two anti-vacuity guards, because a sweep that covers nothing is green: the visit count must equal the table's own lane-row count, and the fixture must contain at least one state whose label bucket differs from its cell.
  • every_lane_state_has_exactly_one_review_section (human-queue --json: counts.* (label bucket) and lanes cells (classifier) measure the same state differently — counts.ready 23 vs an absent lane cell #228) — both directions between the table and the daily review: a lane row with no section is inventory the human is never shown, and a section over a non-lane row lists PRs from a cell that is never emitted.
  • a_stale_at_head_ready_pr_prints_as_un_vetted_not_as_merge (human-queue --json: counts.* (label bucket) and lanes cells (classifier) measure the same state differently — counts.ready 23 vs an absent lane cell #228) — the live 2026-08-06 shape reduced to one PR: a labelled ai:ready PR pushed past its verdict prints under UN-VETTED, and the human's MERGE inbox prints at 0 without listing it.
  • a_retired_states_review_section_appears_only_while_it_has_residue (human-queue --json: counts.* (label bucket) and lanes cells (classifier) measure the same state differently — counts.ready 23 vs an absent lane cell #228) — the review's residue gate, off the row's own Emit: a drained retiree leaves the review while an empty LIVE state still prints at zero.
  • a_deleted_state_leaves_no_descriptor_no_counts_key_and_no_lane_cell — a DELETED state leaves no trace, as a property over a DELETED_STATE_LABELS registry of (label, the counts key it used to emit): no descriptor names it, neither spelling survives in counts, no lane carries a cell, and the classifier treats it as the ordinary un-vetted fallthrough. Both merged deletions (RULED: migrate the ai:blocked-deploy residue to ai:reject — rework each PR for the split release lifecycle #221, RULED: an answered design question routes straight to producer rework, same as a rejection #219) are rows of DATA; the next one costs a row rather than a manual sweep. The counts key is DECLARED beside the label, not derived from it — deriving it was tried first and was wrong, because an ai:* state drops the namespace (blockedDeploy) while a human:* state keeps it (humanDesign), so the inference asserted the LIVE counts.design must not exist. The test caught that on its first run.
  • a_lane_whose_only_states_are_residue_disappears_when_it_drains — what an EMPTY lane does, pinned rather than eyeballed. RULED: an answered design question routes straight to producer rework, same as a rejection #219 left human-decisions holding only the retired human:reject residue, so when it drains the lane is ABSENT from lanes and no descriptor is emitted for it — correct, because lanes is sparse occupancy and the only remaining row is residue-gated; a lane rendering as an empty box would be the permanent dimmed box clarification 1 removes. A straggler brings both back.

Verified at 5abe53e (main merged in twice — #221 and #219): full cargo test green via the flake's rust shell (1021 unit + 78 integration), CI's exact rainix-rs-static (rustfmt + clippy, changed file touched first) exit 0, the rainix pre-commit run --all-files bundle exit 0, nix build .#pr-review-report green (its in-derivation release-profile suite included).

Merge coordination — ai:blocked-deploy (#225 merged 2026-08-07) — DONE

main is merged in at 71ac743 (a merge, never a rebase, so review history stands). The plan below is what was executed, and the trap it names is real: the constant was DELETED, never re-literalised. Giving RETIRED_BLOCKED_DEPLOY_LABEL a string literal to make the tree compile resurrects counts.blockedDeploy and lands green, because rustc only enumerates the remaining consumers when the const is gone.

With it deleted, the compiler surfaced three hard references — the STATE_DESCRIPTORS entry, the REVIEW_LANE_SECTIONS entry (this branch's own), and the label still sitting in the test-side state_label_sets() list. The other two are string literals no compiler can reach and were removed by hand: the golden row in state_descriptors_emit_the_ratified_shape, and the RETIRED_BLOCKED_DEPLOY_LABEL mention in the state-table doc inventory.

counts.blockedDeploy needed no deletion of its own, and this is the one place the plan changed because of #228: counts now derive from the descriptor table, so dropping the row drops the key by construction — there is no "blockedDeploy": … line anywhere that a stale patch could leave behind. Verified, not assumed: main's own subject_ref_tests::the_deleted_blocked_deploy_state_emits_no_count_key passes unchanged, asserting the key is ABSENT (not zero) while blockedInfra/blockedOn/ready/design survive.

The residue fixture in a_residue_row_emits_only_while_occupied is re-pointed at ai:blocked-infra rather than deleted — it exists to pin the gate mechanism, not this label, so deleting it would have quietly retired that coverage.

Two conflicts were resolved against this branch's design rather than main's: the counts block stays descriptor-driven (main's side was the pre-#228 inline label-bucket block, which is exactly what #228 removes), and the daily review's state sections stay in review_lane_sections. One stale assertion from main — count("leak", "leak") == 1 — is removed: #130 clarification 2 retired that cell and this branch already asserts its absence, so the two contradicted each other and the suite caught it.

Verified after the merge: no STATE_BLOCKED_DEPLOY, no RETIRED_BLOCKED_DEPLOY_LABEL, and no descriptor naming the label anywhere in the source; every surviving mention of the string is either a comment explaining the absence, the flag-blocked-deploy refusal that still parses on purpose, or a test pinning that the string models nothing.

Merge coordination — human:design (#224 merged 2026-08-07) — DONE

Merged in at cf8822c. Same discipline as #221 and the same lesson, harder: the compiler is necessary but not sufficient.

STATE_HUMAN_DESIGN, its humanDesign counts key, its REVIEW_LANE_SECTIONS entry and HUMAN_DECISION_LABELS (a const spelled FROM the row) were all deleted, none re-literalised. After that the compiler reported clean while four data-level sites were still wrong, none reachable by type checking: the golden row in state_descriptors_emit_the_ratified_shape, BOTH emitted-key lists in a_residue_row_emits_only_while_occupied, and a descriptor("human:design").emit assertion that would only have blown up at runtime. Found by grep after the compiler pass.

counts.humanDesign needed no deletion of its own — same as blockedDeploy, it is written from the descriptor row, so dropping the row drops the key. Now asserted rather than assumed, by the registry property above.

Conflict shape: four in main.rs plus one in the README. Three resolved to this branch's design (the descriptor machinery, the descriptor-driven counts block, the review's sections). The fourth was hand-merged rather than taken from a side — main's count("human-decisions","human:design") == 0 is correct AND this branch's /leak absence assertion is correct, while main's re-introduced count("leak","leak") == 1 is not.

The consumer half — rendering from these instead of the hand-copied STATES table, plus the loud unclaimed-occupancy defect with the two named frozen-legacy count categories — is the rain-org-health follow-up the issue scopes out.

QA

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added centralized queue-state metadata for consistent ownership, actions, history, occupancy, labels, and reporting.
    • Lane counts, daily review sections, and JSON state descriptors are now generated from shared state definitions.
    • Added clearer handling for close-candidate and leak states outside lane views.
    • Preserved historical count meanings and retained raw label data separately from lane-state counts.
  • Documentation

    • Updated documentation to explain lane-based FSM views, state counts, rollups, and historical records.

Closes #130

A consumer hand-copying this pipeline's state vocabulary drifts silently
in every direction: a state deleted here renders forever at zero there,
an added one never renders, a renamed one flatlines, and a re-laned one
hides real inventory (#161's 17 blocked-on PRs rendering nowhere). The
fix is the machine's shape as data: `human-queue --json` now carries a
top-level `stateDescriptors` array — per state its key, owner, act,
kind, history key, retired-history folds, occupancy source, and display
label — in render order, per the ratified schema contract on #130.

ONE table (amendment 2): the descriptor rows are the same constants
`classify_lane` reads — HUMAN_DECISION_LABELS, VETTER_VERDICT_LABELS
and the three retired-label constants are now spelled from the rows, as
are the classifier's synthesised `un-vetted` and `leak` states — so a
state cannot exist for the classifier and be missing from the emitted
shape. The `ai:close-candidate` hand-off keeps no row of its own: the
flag machinery inventories its subjects into the two close-candidate
rows (#211/#212).

The four states outside `lanes` cells (amendment 1) — uncoveredIssues,
leak, closeCandidateUnvetted, closeCandidateUpheld — declare top-level
`{counts, items, itemsAreIssues}` occupancy; `lanes` stays the sparse
occupancy it was. History folds carry only keys `counts` no longer
emits: `awaitingReVet` into un-vetted (#128) and `closeCandidatePrs`
into the upheld inbox (#211/#212); `humanCloseCandidate` folds nowhere
by design (#213 made close decide+do with no state between).

Conservation is pinned structurally: a sweep built from the classifier's
own label constants asserts every reachable state has exactly one
descriptor and every lane-occupancy descriptor names a reachable cell;
every declared occupancy source and history key must resolve in the
document the same binary emits, and every `counts` key must be claimed
by a descriptor or be a named non-state rollup; the ratified schema is
pinned verbatim, order included.

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

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 59e249bc-6fa7-42de-9d8d-aaa4759a4f32

📥 Commits

Reviewing files that changed from the base of the PR and between cf8822c and d7fd5c0.

📒 Files selected for processing (2)
  • README.md
  • pr-review-report-rs/src/main.rs

Walkthrough

The PR centralizes metadata for queue states. Classification, lane occupancy, counts, review sections, JSON descriptors, history handling, tests, and documentation now use the shared descriptor model.

Changes

State descriptor and queue state flow

Layer / File(s) Summary
Central state descriptor model
pr-review-report-rs/src/main.rs
Defines ordered metadata for queue states, including ownership, kind, occupancy, history folding, labels, emission policies, and JSON serialization. Label constants derive descriptor keys.
Descriptor-driven classification and lane rendering
pr-review-report-rs/src/main.rs
classify_lane uses descriptor-derived keys. Lane cells exclude Leak and CloseCandidate, which remain in top-level inventories. Daily review sections use descriptor-ordered lane populations.
Queue descriptor emission and validation
pr-review-report-rs/src/main.rs, README.md
Queue JSON derives counts from lane cells, preserves raw label buckets under states, emits filtered stateDescriptors, and preserves historical count meanings. Tests cover conservation, schema, ordering, deleted states, residue, leaks, review output, and count semantics.

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

Sequence Diagram(s)

sequenceDiagram
  participant classify_lane
  participant lanes_doc
  participant QueueDocument
  participant StateDescriptor
  classify_lane->>StateDescriptor: resolve descriptor-derived state key
  StateDescriptor-->>classify_lane: return state key
  lanes_doc->>QueueDocument: provide lane-cell populations and top-level inventories
  QueueDocument->>StateDescriptor: serialize occupied descriptors
  StateDescriptor-->>QueueDocument: return ordered stateDescriptors
Loading

Possibly related issues

  • rainlanguage/issue-pr-cron#130 — Covers canonical state-descriptor emission and retired-history support.
  • rainlanguage/rain-org-health#164 — Consumes the emitted stateDescriptors schema.
  • rainlanguage/issue-pr-cron#228 — Covers counts derived from classified lane occupancy.
  • rainlanguage/issue-pr-cron#66 — Covers centralized ownership in state descriptors.

Possibly related PRs

Suggested labels: ai:ready

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding stateDescriptors to human-queue --json.
✨ 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-emit-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.

@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 `@pr-review-report-rs/src/main.rs`:
- Around line 47140-47149: Replace the hard-coded "ai:close-candidate" label in
the label setup loop with the existing PENDING_CLOSE_FLAG constant, keeping the
other labels unchanged so the test uses the transition’s shared definition.
🪄 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: 78632f53-59f8-431c-8bfd-b4b38eb7413b

📥 Commits

Reviewing files that changed from the base of the PR and between eed61cf and d829364.

📒 Files selected for processing (1)
  • pr-review-report-rs/src/main.rs

Comment thread pr-review-report-rs/src/main.rs
claude added 2 commits August 6, 2026 11:06
…ired

Two ratified contract clarifications on #130
(#130 (comment)),
both surfaced by verifying the emission against the consumer's
conservation requirements:

A retired state exists only as its residue (clarification 1). Live rows
always emit — an empty live state stays visible — while a residue row
(ai:relink, ai:blocked-deploy, ai:blocked-infra, human:reject, and
human:design pending its #219 deletion) emits only while its
`counts.<hist>` is nonzero, so a drained retired state leaves the shape
instead of renting a permanent dimmed box (rain-org-health#145's
rejected outcome). The `counts` object is assembled first and the
emission filters against it; no `retired` field reaches the wire, and
the "(retired #n)" suffix stays in `label` while the residue lasts.

Every emitted `lanes` cell is claimed (clarification 2). `lanes_doc` no
longer emits the redundant `lanes.leak` cell: the leak descriptor claims
the top-level `leaks` array — the one carrying each leak's `reason` —
and nothing consumed the cell (the review path prints leaks from the
array; pipeline.html reads counts.leaks/items leaks). The conservation
test now also sweeps the cells `lanes_doc` actually emits and requires
each to be claimed by exactly one lane-occupancy descriptor, zero
declared exceptions.

The schema pin moves to full occupancy (all rows emitted) and a new test
pins the gate: the live set at zero occupancy, a residue row returning
at its table position with its label intact, and human:design gating
identically with no special-casing.

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

`human:reject` (#133 — a reject is a reject whoever ruled it) and
`ai:relink` (#135 — a linkage error IS a reject) were consolidated INTO
`ai:reject` by exactly the rename shape #130's history paragraph is
about: "a descriptor needs to be able to say 'my series also draws from
these retired keys', or a rename quietly truncates every chart that
spans it". `un-vetted` already folds `awaitingReVet` for the same
reason. Without the fold, descriptor mode truncated the reject
sparkline's past relative to the frozen fallback — inventory that sat
under `humanReject`/`relink` vanished from the series.

`STATE_REJECT.hist_fold` is now `["humanReject", "relink"]`, and the two
absorbed residue rows carry NO series of their own: `hist` is an
`Option`, `None` for them, omitted from the wire rather than emitted as
null. The successor draws the past, so the same inventory never draws in
two boxes — rain-org-health#162's design language for absorbed
retirees. They still gate on their kept-while-nonzero counts, so the
straggler stays visible as a state: `Emit::WhileOccupied` carries its
gate key rather than reading `hist`, which keeps the gate and the series
claim independent.

The conservation checks stay coherent and get stricter: a fold key must
not be any row's claimed `hist` (an ownership transfer, not a share) and
must fold into exactly one series; every gate key must be an emitted
count; and the counts-claim sweep now accepts a key claimed as a FOLDED
past, so `humanReject`/`relink` are claimed rather than unclaimed while
they still emit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude and others added 2 commits August 7, 2026 07:18
Two defects found by adversarial review, both proven by probe.

`human:design` was gated as residue although it is LIVE: `/design` still
writes the label and #111 routes each delegated ruling back to the
producer, so the population empties and refills in the ordinary course.
Gating it made the box vanish the moment the queue drained and reappear
on the next ruling — a live state rendering nowhere, the mirror of the
drift #130 exists to kill. It is now `Emit::Always`; clarification 1 is
explicit that only a state whose existence IS its residue may be gated.
(#219 proposes deleting the state, which removes the row wholesale — a
different thing from gating a state the machine still writes.)

The `ai:blocked-deploy` / `ai:blocked-infra` gates measured a DIFFERENT
POPULATION from the occupancy they gate: `counts.blockedDeploy` counts
the first `ai:*` label (`ai_state_label`) while the declared occupancy
is the `classify_lane` PRECEDENCE cell. Both directions were wrong on
two-label PRs, which `classify_lane`'s own comments call normal:
`["human:design", "ai:blocked-deploy"]` gave count 1 over an EMPTY cell
— a permanent dimmed box holding nothing, exactly what clarification 1
removes — and `["ai:ready", "ai:blocked-deploy"]` gave count 0 over a
FULL cell, hiding a real PR from a consumer whose conservation check
then fires. Rather than re-point those two keys, the gate now reads the
row's DECLARED occupancy (`descriptor_occupancy`, the same place it
points consumers), so `Emit` carries no key of its own and gate and
occupancy cannot diverge by construction.

The conservation sweep now asserts on the WIRE: it builds a document
from a fixture that INCLUDES multi-label PRs — every ordered pair of
state labels, the only inputs where a label-bucket key and a precedence
winner disagree — and checks `stateDescriptors` AS EMITTED, through the
`Emit` filter, against the emitted `lanes`. Both probe cases passed the
old table-level sweep unchanged. It runs both directions: no emitted
cell unclaimed, and no residue row emitted over an empty cell.

Also: `counts` is completed with `openIssues` BEFORE the emission reads
it, so a future gate on a late-added key cannot read absent-as-zero; and
`lanes_doc`'s comment no longer claims it emits a `leak` cell three
lines above the code that skips it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`counts.<state>` was computed from the LABEL BUCKET (`ai_state_label`, the
first `ai:*` label a PR carries) while `lanes[<lane>][<state>]` was computed
from `classify_lane` PRECEDENCE. Both populations are defensible and nothing in
the document said they measure different things, so it answered "how many PRs
are in this state?" twice with different numbers and nothing compared them. On
the 2026-08-06 snapshot that was `counts.ready` 23 over an ABSENT
`vetter-verdicts`.`ai:ready` cell — every one of the 23 un-vetted at its
current head — which the dashboard drew as a merge inbox of 0 beneath a
sparkline plotted at 23.

Same failure class as the residue-emission gate this branch already fixed, one
layer down, so it is fixed the same way: `StateOccupancy::Lane` now names the
`counts` key its cell fills, and `lane_counts_json` writes every such key
through `descriptor_occupancy` — the accessor `Emit::WhileOccupied` reads. A
key, its gate and its rendered cell are one number by construction. Ten keys
derive this way: the five that were the label bucket (`ready`, `design`,
`blockedOn`, `blockedInfra`, `blockedDeploy`) and the five that already read a
cell through their own hand-written `lane_state_count(…)` call, which are gone
— a re-laned state must carry its key rather than need a second edit.

The label population needs no key of its own: the top-level `states` object IS
that census, click-through, and has never meant anything else. A second
`counts.*` for it would restate `states.<label>.length` and land unclaimed
under this branch's counts-claim sweep.

The daily human-readable review had the same defect: four sections printed the
bucket, so the tool disagreed with the `--json` the same run emits. They are
now `review_lane_sections(lanes)`, handed the cells and nothing else, so there
is no second population in scope to print. Each section names its descriptor
row, and a residue section is gated by that row's own `Emit`.

`human-queue-history.jsonl` copies `counts` verbatim, so `ready`, `design`,
`blockedOn`, `blockedInfra` and `blockedDeploy` change meaning at this commit.
The past is NOT rewritten — those lines are true measurements of the machine as
it then was. Only `ready` steps visibly (23 → 0 on the 2026-08-06 snapshot);
the inventory did not move, the measurement did. Stated in `lane_counts_json`,
on `queue_history_line` (the one writer of every line in that file) and in the
README, rather than left as a silent step in the chart.

Closes #228

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 `@README.md`:
- Around line 1356-1359: Update the README description of the listed counts keys
to call them “lane-state counts” rather than the “full-machine view,” since
additional keys such as closeCandidateUnvetted and closeCandidateUpheld are
documented elsewhere. Keep the existing key list unchanged.
🪄 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: b34e6421-ac11-4302-ba36-ba0cac26357d

📥 Commits

Reviewing files that changed from the base of the PR and between fb5ad79 and 3e31657.

📒 Files selected for processing (2)
  • README.md
  • pr-review-report-rs/src/main.rs

Comment thread README.md Outdated
thedavidmeister and others added 3 commits August 7, 2026 09:22
The list names every key derived from a lane cell, and calling it the
full-machine view read as the complete `counts` schema — the close-candidate
split and the non-state rollups sit beside it and measure top-level arrays, not
lanes. Names what the list is and what it excludes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR 225 deleted the state entirely. This branch carried five resurrection
sites, and the trap is that only ONE of them is reachable by the compiler
unless `RETIRED_BLOCKED_DEPLOY_LABEL` is DELETED rather than re-literalised —
give it a string literal to make the tree compile and `counts.blockedDeploy`
comes back green.

The constant is deleted. rustc then walked to three hard references: the
`STATE_DESCRIPTORS` entry, the `REVIEW_LANE_SECTIONS` entry, and the label
still sitting in the test-side `state_label_sets()` list. The rest are string
literals no compiler can see, and were removed by hand: the golden entry in
`state_descriptors_emit_the_ratified_shape`, and the state-table doc inventory.

`counts.blockedDeploy` needed no deletion of its own. Since counts derive from
the descriptor table, dropping the row drops the key by construction — there is
no `"blockedDeploy": …` line anywhere to survive a stale patch. That is
verified, not assumed: main's own
`the_deleted_state_leaves_no_blockedDeploy_key` passes unchanged.

The residue fixture in `a_residue_row_emits_only_while_occupied` is RE-POINTED
at `ai:blocked-infra` rather than deleted: it exists to pin the gate mechanism,
not this label, so deleting it would have quietly retired the coverage.

Two conflicts resolved against this branch's own design: the counts block stays
descriptor-driven (main's side was the pre-#228 inline label-bucket block), and
the daily review's state sections stay in `review_lane_sections`. One stale
assertion from main — `count("leak", "leak") == 1` — is removed: #130
clarification 2 retired that cell, and the branch already asserts its absence.

Comments that narrated the deleted state as a live hazard are rewritten to
describe current behavior: the `Emit` gate's rationale no longer cites a
`counts.blockedDeploy` divergence that cannot now be constructed, and the
history-series note records `blockedDeploy` as a series that ENDS rather than
one whose meaning changed.

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

PR 224 deleted `human:design` from both subject types. This branch carried its
descriptor row (`Emit::Always`), its `humanDesign` counts key, its
`REVIEW_LANE_SECTIONS` entry, and `HUMAN_DECISION_LABELS` — a const spelled
from the row. All deleted, none re-literalised, so rustc had to walk to the
consumers rather than being quietly satisfied.

The compiler was NECESSARY BUT NOT SUFFICIENT, again. After the deletions it
reported clean while four data-level sites were still wrong, none of them
reachable by type checking: the golden row in
`state_descriptors_emit_the_ratified_shape`, both emitted-key lists in
`a_residue_row_emits_only_while_occupied`, and a
`descriptor("human:design").emit` assertion that would only have blown up at
runtime. Found by grep after the compiler pass.

So the hand-checks become coverage instead, because hand verification does not
survive the next merge:

- `DELETED_STATE_LABELS` is a registry of `(label, the counts key it used to
  emit)`, and `a_deleted_state_leaves_no_descriptor_no_counts_key_and_no_lane_cell`
  asserts over it: no descriptor names the label, neither spelling survives in
  `counts`, no lane carries a cell, and the classifier treats it as the ordinary
  un-vetted fallthrough. Both merged deletions are now rows of data; the next
  one costs a row, not a sweep.

  The key is DECLARED beside the label, not derived from it. Deriving it was the
  first thing tried and it was wrong: `ai:*` states drop the namespace
  (`ai:blocked-deploy` → `blockedDeploy`) while `human:*` states keep it
  (`human:design` → `humanDesign`), so the inference asserted that
  `counts.design` — the LIVE `ai:design` series — must not exist. The test
  caught that on its first run.

- `a_lane_whose_only_states_are_residue_disappears_when_it_drains` pins what an
  empty lane does rather than leaving it to be eyeballed. #219 left
  `human-decisions` holding only the retired `human:reject` residue, so when
  that drains the lane is ABSENT from `lanes` and no descriptor is emitted for
  it — correct, because `lanes` is sparse occupancy and the only remaining row
  is residue-gated. A straggler brings both back.

- The `un-vetted` fixture total is DERIVED from the fixture and the registry
  instead of hand-written. A literal there is right on each side of a merge and
  wrong once two deletions land, with no conflict marker on the line — which is
  exactly how it sat at `3` over a four-member bucket.

Conflicts resolved against this branch's design: the counts block stays
descriptor-driven, the review's sections stay in `review_lane_sections`, and the
`lanes.leak` assertion main reintroduced is dropped again (#130 clarification 2
retired that cell). Conflict 4 was hand-merged rather than taken from a side:
main's `human:design` cell assertion is correct AND this branch's `/leak`
assertion is correct.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

113-118: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the full pr-review-report invocation in both command-documentation sites.

The README currently presents transition names without the required executable wrapper. This makes copied commands non-executable.

  • README.md#L113-L118: Prefix every transition in the table with pr-review-report.
  • README.md#L299-L299: Prefix human-rule … design --rework or identify it as an internal subcommand name.

As per coding guidelines, command documentation must invoke pr-review-report for all transitions.

🤖 Prompt for 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.

In `@README.md` around lines 113 - 118, Update README.md lines 113-118 so every
transition command is shown with the full pr-review-report executable prefix.
Also update README.md line 299 to use the full pr-review-report invocation for
the human-rule … design --rework command, or explicitly identify it as an
internal subcommand; ensure all command documentation is directly executable or
clearly marked internal.

Source: Coding guidelines

🤖 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 `@README.md`:
- Around line 1366-1370: Revise the “One population per state” documentation to
apply the lane-cell inventory and counts-key explanation only to lane state
descriptors. Add the contrasting StateOccupancy::Counts behavior, clarifying
that top-level arrays such as closeCandidateUnvetted and closeCandidateUpheld
determine counts by their length.

---

Outside diff comments:
In `@README.md`:
- Around line 113-118: Update README.md lines 113-118 so every transition
command is shown with the full pr-review-report executable prefix. Also update
README.md line 299 to use the full pr-review-report invocation for the
human-rule … design --rework command, or explicitly identify it as an internal
subcommand; ensure all command documentation is directly executable or clearly
marked internal.
🪄 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: d9237d79-0a27-4fe7-860c-88db7b0fbfdf

📥 Commits

Reviewing files that changed from the base of the PR and between 3e31657 and cf8822c.

📒 Files selected for processing (2)
  • README.md
  • pr-review-report-rs/src/main.rs
💤 Files with no reviewable changes (1)
  • pr-review-report-rs/src/main.rs

Comment thread README.md Outdated
The section said a state's inventory is its lane cell, which is only true of
LANE states. A top-level state (`uncoveredIssues`, `leak`, and the two
close-candidate inboxes) declares `StateOccupancy::Counts`: its inventory is a
top-level array and its key IS that array's length. Both directions named, so
the wording matches the two-variant declaration the code actually has.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main added two MCP tools and their commands; this branch changes no tool
surface, so main's tool table, command lists, plugin manifest and the counts
that measure them (22 tools, 7 narrowing) carry over untouched.

The one conflict is `lanes_doc_emits_every_state_with_the_right_members`,
where both sides replaced the same hand-written totals for the same reason —
a literal that is right on each side of a merge and wrong once both land.
Main's is the more general mechanism: each fixture row carries its own
expected `(lane, state)` and every total is counted off those annotations, so
it is kept and used throughout. This branch's two semantic claims are applied
on top of it:

- the `leak` bucket emits NO lane cell (#130 clarification 2) — its inventory
  is the top-level `leaks` array, which is what its descriptor declares as its
  occupancy. Row #14 is therefore annotated `NO_LANE`, the same hand-off #7
  already had, and the retirement is asserted both ways: the annotations
  expect no `leak` cell, and the emitted doc has no `leak` key at all;
- the un-vetted total keeps its second, independent reading off the
  `DELETED_STATE_LABELS` registry beside main's annotation count. The
  annotations say where each PR lands; the registry says why the bucket is
  that size, so deleting a state without retiring its label fails here instead
  of quietly re-annotating itself.

`next_leak` is unaffected by the retired lane cell: it builds its rows from
`producer_pr_inventory` through `is_leak_candidate` and `classify_lane`, never
from `lanes_doc` — which is the property #222's own test pins.

The cross-lane total is now counted off the `NO_LANE` annotations rather than
written as `prs.len() - 2`, because that subtraction is exactly the shape that
survives a merge silently while the fixture hands off a different number.

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

Copy link
Copy Markdown
Contributor Author

Reviewed d7fd5c0: approve — read the diff and verified the merge independently. This emits stateDescriptors — the machine's shape as data, derived from the same constants classify_lane reads, so deleting a state removes its descriptor by construction rather than by anyone remembering. It also derives every lane state's counts key from that state's own lane cell through the same descriptor_occupancy accessor the emission gate reads, which makes the label-bucket/classifier divergence unconstructible rather than merely corrected: counts.ready stops reading 23 against an empty cell, and the four review sections that printed the bucket stop contradicting the JSON. Residue rows emit only while occupied, so a drained retired state leaves the shape instead of renting a permanent dimmed box; ai:reject folds humanReject+relink so the rename does not truncate the series; and the redundant lanes.leak cell is gone, verified safe because next_leak builds from producer_pr_inventory/is_leak_candidate/classify_lane and never reads lanes_doc. Merge verified by hand: zero occurrences of RETIRED_BLOCKED_DEPLOY_LABEL or HUMAN_DECISION_LABELS, both deleted states surviving only as DATA in the DELETED_STATE_LABELS registry, and main's next_leak/next_design intact at 46/49 references. CI green on the merge commit across both rs-test legs, rs-static and nix-build; 0 unresolved threads.
Rulings-conformance: checked against CLAUDE.md and every ruling stated for this work. (1) issue 130's ratified contract and both clarifications — descriptors derived from the classifier's own constants, residue emitted only while occupied, every emitted lane cell claimed. (2) issue 228 — one source of truth per state, with a structural agreement test iterating the shared table rather than a hand list. (3) 'the prs are open, trying to get them right' — 228 was folded in rather than filed as a follow-up. (4) 'can we get test coverage rather than manual verification' — the hand-checks became a DELETED_STATE_LABELS registry property, an empty-lane pin, and a derived fixture total that reproduces and kills the count-outside-the-markers bug. (5) removal-is-semantic-not-grep — the constant was deleted, not re-literalised, and the grep sweep after the compiler pass found four data-level sites rustc reported clean on. The artifact obeys each.

@thedavidmeister
thedavidmeister merged commit 0ac8949 into main Aug 7, 2026
21 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

2 participants