Skip to content

Carry next_design's split on the human-queue design lane, so the dashboard can show the inbox apart from the defect - #243

Merged
thedavidmeister merged 2 commits into
mainfrom
design-lane-split-240
Aug 9, 2026
Merged

thedavidmeister merged 2 commits into
mainfrom
design-lane-split-240

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes #240

Pairs with rainlanguage/rain-org-health#168 — the renderer half. The two land together: this PR emits the split, that one draws it. Neither is harmful alone (an old dashboard ignores the new fields; the new dashboard falls back on a snapshot without them), but the number on the panel is only fixed when both are in.

What the mismatch was

Measured 2026-08-09T08:44Z, same moment, both tools: the dashboard said 6 design PRs waiting on a human; /ndd said presentable 0, noQuestion 6. The snapshot's lanes["vetter-verdicts"]["ai:design"] carried the raw label total, and next_design withheld all six as "no trusted comment raises a design question". Draining the whole ai:design queue that day did not move the box, because the number it drew was never the number of rulable rows.

The schema

human-queue --json now attaches next_design's own partition to the design cell:

"lanes": { "vetter-verdicts": { "ai:design": {
  "count": 6,                       // UNCHANGED — the raw label total, for every existing consumer
  "breakdown": {                    // NEW — every bucket, zeros included
    "presentable": 0,               //   what /ndd will serve: the human inbox
    "noQuestion": 6,                //   labelled, no trusted question behind it: a defect bucket
    "draft": 0, "unaddressable": 0, "fetchErrors": 0
  },
  "prs": [ { "repo": "…", "number": 412, "url": "…", "title": "…",
             "bucket": "noQuestion" } ]   // NEW — one bucket per entry, same spellings
} } }

plus two counts keys mirroring the headline buckets off that same breakdown:

"counts": { "design": 6, "designPresentable": 0, "designNoQuestion": 6 }

counts.design keeps its exact meaning (the raw cell size, per #228), so the existing series is continuous. The rollup copies counts verbatim, so queue-history-line carries the two new series with no change to the appender or to refresh-human-queue.sh.

One enumeration, one classifier. The split does not re-implement "does a trusted comment raise a question". It routes each cell member through next_design's own stages — nd_hit_class first (so a draft is withheld fetch-free, exactly as the live queue does it), then nd_outcome over the same gh pr view read. Both matches are exhaustive on purpose: a new arm in either stage refuses to compile until the split gives it a bucket. The members are selected by the same classify_lane call lanes_doc buckets with, never by a label read, so the split describes exactly the list it is emitted beside (a PR wearing ai:design under ai:close-candidate is in neither).

breakdown.<key> is COUNTED OFF the annotated entries rather than copied from the split, so it equals the number of prs rows carrying that bucket by construction.

Cost: one gh pr view per design-lane member, --json only — the daily review prints the cell whole and pays nothing.

Out of scope, per the issue: the search-index lag on a fresh ruling. No retry loop, and no log line either — refresh-human-queue.sh has no non-trivial place to put one that says more than the existing stamped lines already do.

QA

  • Discriminating tests: design_split_tests::{the_members_are_the_cells_own_prs, a_member_lands_where_ndd_would_put_it, the_breakdown_partitions_the_cell_it_is_attached_to, a_draft_member_is_filed_apart_without_a_read, an_empty_design_lane_emits_zeroes_not_a_cell, the_counts_mirror_the_breakdown_and_the_raw_series_survives} — all six name functions that do not exist on base, so they cannot compile against it; their discrimination is established by the mutation pass below rather than by a base run. state_descriptor_tests::every_descriptor_occupancy_source_resolves_in_the_emitted_document (pre-existing, extended here) fails on base-plus-emitter-removed — verified as mutant M8, which it names in its own words: "counts.designPresentable must emit even over an empty design lane".
  • Mutations applied: 8 mutants over the changed lines, 8/8 killed, 0 survivors.
    • M1 design_lane_members reads the label instead of calling classify_lane → killed by the_members_are_the_cells_own_prs (the dominated ai:close-candidate / ai:blocked-on members enter the split but not the cell)
    • M2 design_bucket swaps Presentable/NoQuestion → killed by a_member_lands_where_ndd_would_put_it
    • M3 design_member_hit hardcodes isDraft: false → killed by a_draft_member_is_filed_apart_without_a_read (its fetch seam asserts the draft is never read)
    • M4 attach_design_breakdown counts but never annotates the entries → killed by the_breakdown_partitions_the_cell_it_is_attached_to
    • M5 design_breakdown_count reads the cell's count instead of its breakdown → killed by the_counts_mirror_the_breakdown_and_the_raw_series_survives
    • M6 the two counts keys are wired to each other's bucket → killed by the same test (the fixture is deliberately asymmetric, 1 presentable vs 2 noQuestion, so a swap cannot pass)
    • M7 attach_design_breakdown drops the zero-valued keys → killed by the_breakdown_partitions_the_cell_it_is_attached_to (key set derived from DesignBucket::ALL, not typed twice)
    • M8 the counts mirror loop is deleted → killed by an_empty_design_lane_emits_zeroes_not_a_cell, the_counts_mirror_the_breakdown_and_the_raw_series_survives, and the extended every_descriptor_occupancy_source_resolves_in_the_emitted_document
  • Oracle: next_design's own stated invariant, aiDesign == draft + unaddressable + presentable + noQuestion + fetchErrors + archivedRepo (the DesignQueueCounts doc comment), and the live 2026-08-09 measurement in the issue (6 labelled / 0 presentable / 6 noQuestion) reproduced as the fixture shape. Fixture comment bodies are built by the REAL writers (state_comment, verdict_comment), so they cannot drift from what the pipeline actually posts. archivedRepo has no twin here and the reason is structural, not an omission: producer_pr_inventory withholds archived-repo PRs before any lane is built, so this cell can never hold one.
  • Category check: issue asks (a) the snapshot carry the same breakdown next_design computes, through the same classifier — done, via nd_hit_class/nd_outcome; (b) count kept for backward compat with the split beside it — done; (c) the prs list split the same way — done, as a per-entry bucket annotation, which is what lets a consumer filter one list rather than reconcile several; (d) queue-history-line carrying the new keys so the time-series can draw them — done, and it needed no change at all, since it copies counts verbatim; (e) the dashboard rendering — the paired rain-org-health PR.

Summary by CodeRabbit

  • New Features

    • Added draft-status information to pull request inventory results.
    • Added design-lane categorization for presentable, no-question, draft, unaddressable, and fetch-error items.
    • Expanded JSON output with per-item categories and zero-inclusive summary breakdowns.
    • Preserved overall design counts while adding separate presentable and no-question totals.
  • Bug Fixes

    • Improved handling of empty design lanes and draft pull requests.
    • Added validation to ensure categorized results remain consistent.

The dashboard's design box and /ndd disagreed about how much design work
waits on a human: the snapshot's lanes cell carried the raw ai:design
label total (6 on 2026-08-09) while next_design presented 0, withholding
every row as 'no trusted comment raises a design question'. A count that
cannot be driven to zero by doing the work it names is not measuring the
work.

human-queue --json now attaches next_design's own partition to the
vetter-verdicts.ai:design cell: a breakdown object beside count
(presentable / noQuestion / draft / unaddressable / fetchErrors, zeros
included) and a bucket annotation on each prs entry, spelled identically
so breakdown.<key> counts exactly the entries annotated <key>. The split
routes through next_design's own classifier stages (nd_hit_class, then
nd_outcome over the same gh pr view read) — one enumeration, one
classifier; both matches are exhaustive so a new arm in either stage
refuses to compile until the split gives it a bucket. count stays the raw
label total for every existing consumer.

counts.designPresentable / counts.designNoQuestion mirror the two
headline buckets off the cell's own breakdown, so the history rollup —
which copies counts verbatim — carries the series the dashboard draws:
the human inbox (what /ndd serves) and the defect bucket (rows no command
serves). counts.design keeps its raw meaning and its continuity.

The producer-PR search now also returns isDraft, carried through
QueuePr, so the draft withholding classifies fetch-free exactly as
next_design's own search-stage does.

Refs #240

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

coderabbitai Bot commented Aug 9, 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: 50 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: 4fb37d43-c71f-4437-a267-640097f5b9fa

📥 Commits

Reviewing files that changed from the base of the PR and between 4adfe31 and a23b8e5.

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

Walkthrough

The PR tracks draft state through PR inventory records and adds JSON-only design-lane buckets. It annotates design records, emits zero-inclusive breakdowns, and adds presentable and no-question counts while retaining the raw design count.

Changes

Design lane inventory

Layer / File(s) Summary
Propagate draft state
pr-review-report-rs/src/main.rs
GitHub search results now provide draft state. Inventory tuples and QueuePr records preserve the parsed value and default missing or invalid values to non-draft.
Classify design buckets
pr-review-report-rs/src/main.rs
JSON output selects design members, classifies them with the existing design classifier, performs bounded detail reads, annotates records, and generates zero-inclusive bucket breakdowns.
Expose and validate design counts
pr-review-report-rs/src/main.rs
Output retains the raw design count and adds designPresentable and designNoQuestion. Documentation, fixtures, and count validation cover the expanded shape.

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

Sequence Diagram(s)

sequenceDiagram
  participant HumanQueueJson
  participant DesignSplitPipeline
  participant DesignClassifier
  participant GitHubDetailReads
  HumanQueueJson->>DesignSplitPipeline: request design breakdown
  DesignSplitPipeline->>DesignClassifier: classify design PR
  DesignSplitPipeline->>GitHubDetailReads: fetch bounded PR details
  GitHubDetailReads-->>DesignSplitPipeline: return detail results
  DesignSplitPipeline-->>HumanQueueJson: return bucket annotations and breakdown
Loading

Possibly related PRs

Suggested labels: ai:ready

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes implement issue #240 by separating presentable work, noQuestion defects, other buckets, raw counts, snapshot data, and history counts.
Out of Scope Changes check ✅ Passed The reported changes support issue #240 and its testing requirements; no unrelated scope is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: splitting the design lane so the dashboard separates the human inbox from defects.
✨ 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 design-lane-split-240

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 17443-17465: Update DesignBucket::ALL to be generated through an
exhaustive match over every DesignBucket variant, rather than maintaining an
independent literal array. Preserve the existing ordering and ensure adding a
new variant causes compilation to require updating this enumeration, keeping
breakdown zero-inclusive.
🪄 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: 4c117b30-b22f-4429-9204-852abc16c68a

📥 Commits

Reviewing files that changed from the base of the PR and between 597a13c and 4adfe31.

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

Comment thread pr-review-report-rs/src/main.rs
CodeRabbit on #243: a new variant makes key() fail to compile, but ALL
compiled unchanged — so design_bucket would classify into the new bucket
and annotate an entry with it while breakdown omitted its key, which is
exactly the zero-inclusive contract the enum exists to state, broken
quietly.

ALL is now declared at length COUNT and checked against an exhaustive
const fn ordinal() by a const block asserting every ordinal is listed
exactly once. Four ways to get it wrong, four build failures, each
verified:
  variant added, nothing else      -> E0004 non-exhaustive ordinal()
  variant + ordinal + key + COUNT  -> E0308 array length 5 != 6
  COUNT raised alone               -> E0308 array length 5 != 6
  a variant listed twice in ALL    -> E0080 'listed twice in ALL'

Refs #240

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

Copy link
Copy Markdown
Contributor Author

Reviewed a23b8e5: pass — members are selected by the same classify_lane call lanes_doc buckets with and each is bucketed through nd_hit_class then nd_outcome, so the split is next_design's own stages rather than a second detector that could drift. DesignBucket::ALL/ordinal/COUNT are pinned by a const-eval assertion, so a new variant cannot silently stop emitting its key at zero. Breakdown seeds every bucket from ALL and counts off the annotated entries; checked lanes_doc builds count: items.len() with no truncation, so those entries are the whole population and the breakdown cannot read short of count. Fan-out via map_bounded. count keeps its prior meaning and the new keys are additive, so an old consumer is unaffected. 8/8 mutants killed.

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed a23b8e5: pass

Rulings-conformance: checked the artifact against CLAUDE.md's rulings section and against every ruling the human stated for this work.

Merging with --merge --admin per the standing no-squash rule; all checks green, no red to account for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard design-lane count must match /ndd: presentable is the inbox, noQuestion is a defect bucket

1 participant