ai:blocked-deploy: migrated as a once-off, state deleted - #225
Conversation
… split-lifecycle rework order Executes the 2026-08-06 ruling in #221 (verbatim: "migrate all the blocked on deploy back to rejected so it can be reworked to fit in the new deployment paradigm"). The new one-shot mirrors migrate-reject (#133): it enumerates every open PR still carrying the RETIRED ai:blocked-deploy label (#162) live at run time, and per PR posts the sha-pinned 👤 human ruling comment recording that the write executes the ruling, posts the trusted Rework note carrying the uniform split-release-lifecycle work order (the tool owns both markers — a malformed record is unconstructible), adds ai:reject, strips other stale ai:* labels, and removes the retired label LAST, so a mid-sequence failure leaves the PR more parked than it started, never less. Guards per the existing plan machinery: closed/merged is moot (nothing written), a live close-candidate flag is reported and skipped (the strand rule), no head sha is refused for that PR. Defaults to a report; --apply gates the write; --dry-run is the explicit spelling of the default. Idempotent: both comments dedup, a migrated PR re-reads as out of the population. README: the producer-blocked residue's exit and the diagram note now record the migration, which joins migrate-reject in the migrations-not-transitions register. In-code prose describing the superseded eyes-on-pass exit is updated to name the migration. Closes #221 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 25 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 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
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. Comment |
…e siblings are listed Three things, all reconciliation with what landed and what CI actually runs. The rust workflow's static job runs `pre-commit run --all-files` after rainix-rs-static, and its `denofmt` hook formats markdown. It skipped locally while nm.md was untracked, so the reflow only surfaced in CI as a red static job. The hook's own output is now committed, and a second run is clean. /nd (PR 226) bumped human-fsm to 0.12.0 for its own command, which is the version this branch had claimed. Two open PRs cannot ship the same version — `/plugin` detects updates by comparing version STRINGS, so the second to land would serve stale content under a version installers have already seen. This goes to 0.13.0, which is past /nd's whichever order the two land in. And the three places that ENUMERATE the human's surface — CLAUDE.md's profile table, README's human-profile paragraph, the plugin README's command table and reads section — now name next_leak and /nm, as /nd named next_design and /nd. An enumeration that omits a shipped tool is wrong the moment its PR lands, and these lists are what a reader consults instead of the tool table. nm.md's "wrong state record" finding also picks up the retired-label case: a label the FSM has deleted lands a PR in this queue exactly as an absent one does. Written as the property rather than naming a label, because the deletion in flight (#225's ai:blocked-deploy) is not the only one that will ever happen and this command should not need editing for the next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ady executed Ruling revision (2026-08-06, verbatim): #221 (comment) "a subcommand? i just want this migrated as a once off so we can delete the state entirely". The migration ran once, from this branch's earlier build, over the live population of 13 PRs — its transcripts are in the PR body and are the once-off's permanent record. What merges is the tree AFTER it: no subcommand, no state. - The `migrate-blocked-deploy` subcommand is gone — Cmd variant, dispatch, plan/steps/search machinery, its tests. Machinery for an empty population is dead weight. - `ai:blocked-deploy` is deleted from the FSM, compiler-first: the constant went first and rustc enumerated every consumer — the classify_lane arm, the blockedDeploy counts key, the human-queue display section, the label_meta row, the next_action parking arm. The label now classifies as NOTHING: unmodelled, so a PR wearing it takes the ordinary fallthrough (producer-commented -> leak, the /nm queue; otherwise un-vetted), parks nothing, and blocks nothing. - The label DEFINITION is deleted from all 6 repos that carried it across the three orgs (zero open PRs were wearing it, so nothing was destroyed). - README and the campaign prompt drop the residue/migration prose; #162's live rule stays: a deploy-shaped block means the REPO has not migrated, expressed as a typed ai:blocked-on. Closes #221 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deletion-scoped mutation pass found this gap: resurrecting the `ai:blocked-deploy` row in `label_meta` SURVIVED the whole suite. That table feeds `gh label create --force`, so a surviving row is what would let the next thing that named the string re-create the definitions this PR deleted from all six repos. Strengthened in place, on the existing retirement test, against an arbitrary unknown label rather than literal bytes — with the deliberate contrast that retired-but-occupied `ai:blocked-infra` KEEPS its row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review caught that the "surfaces as a leak" outcome is UNREACHABLE in production, and the tests had been shaped to reach it. `human_queue_mode` buckets every `ai:*`-labelled PR through `ai_state_label` and leak-detects only the `unlabeled` remainder, so `leak_keys` — the sole source of `producer_commented` — can never contain a PR carrying an `ai:*` label. `classify_lane`'s own contract says the flag is "for a label-less PR". So a PR hand-wearing the deleted `ai:blocked-deploy` string is `(VetLifecycle, "un-vetted")`: the vetter absorbs it and the verdict that judges it strips the dead label. Benign, but not what the tests, the comments or the README claimed. - `the_deleted_blocked_deploy_label_classifies_as_nothing` drops the unreachable `producer_commented: true` assertion and documents WHY false is the only constructible input. - `lanes_doc_emits_every_state_with_the_right_members` feeds the fixture the way the pipeline feeds it (`qpr(8, …, false)`) and asserts the un-vetted outcome — including #8 by number in the un-vetted member list, so a regression in how unmodelled `ai:*` labels route is now visible. Restores `count("leak","leak") == 1`. - README's producer-blocked lane doc states the un-vetted truth and says why it is not a leak. - NITs: process narration dropped from the classify_lane test doc; the label_meta comment no longer overstates a missing row (the function is total — an absent row yields the generic fallback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed fdf2ee0: approve — read the full diff independently. The once-off executed (13 PRs migrated, transcripts in the body) and the subcommand that ran it is gone: zero occurrences of |
…ranch Two independent DELETIONS of an FSM state landed on the same regions: #221/#225 deleted `ai:blocked-deploy`, this branch deletes `human:design`. Every conflict was that symmetry, and every one takes BOTH deletions rather than a side. - classify_lane's lanes fixture now carries residue of both deleted labels, so the un-vetted bucket gains a member from each: count 3 -> 4, members [1, 2, 8, 12]. Neither deleted state emits a lane cell. - the domination case keeps this branch's subject (the retired `human:reject` is the human-decisions lane's one remaining state, not the deleted `human:design`) over main's, and main's spelling of the deploy label: a bare literal, because #225 deleted the constant and re-declaring one to name a state that does not exist would be the deletion undone. - README's lane list takes main's producer-blocked bullet whole and this branch's human-decisions bullet whole. next_action auto-merged correctly and is the interaction worth stating: main removed the deleted label from the modeled-state park arm, this branch added the reject arm directly beneath it, and the result keeps both — a note-less `ai:reject` still classifies parked-skip and nothing parks on the deleted deploy label. The derived action vocabulary needed no edit at all: #225 added and retired no NextAction variant, and the exhaustive rank()/names_work() matches would have refused to compile if it had. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…) into /nm Both deletions land under a PR whose whole subject is "which PRs are in no modeled state", so the interactions are the merge, not a side effect of it. CONFLICT SHAPE: two files, both the plugin version JSONs, both trivial — main carries #224's 0.12.0 and this branch 0.13.0, and mine is the superset (it names /nm) and is already ahead. main.rs auto-merged CLEAN, which is exactly the case that has bitten twice today, so the real work was hunting what the markers could not show. WHAT THE SUITE CAUGHT THAT THE MARKERS DID NOT: - The drift witness in `a_row_carries_the_leaks_own_evidence` asserted a `human:design` PR classifies into the human-decisions lane. #224 deleted that state, so it now classifies as a LEAK and the assertion failed on the merge commit. Repointed at `human:reject` — the lane's one surviving state, and still the live instance of the hazard the field exists for (a `human:*` label carries no `ai:*` prefix, which is what the old population wrongly admitted). - `is_leak_candidate` referenced `RETIRED_BLOCKED_DEPLOY_LABEL`, deleted by #225: a compile error, fixed by dropping the row. WHAT ONLY READING #225's OWN COMMIT CAUGHT — and no test would have: `fdf2ee0` ruled that a PR hand-wearing a DELETED `ai:*` string lands `un-vetted`, not `leak`, because the vetter absorbs it and the verdict that judges it strips the dead label. That ruling was justified by the caller-side `ai_state_label` partition — the exact thing this PR replaced with a classifier-derived population. Under the merge as it auto-resolved, such a PR became a leak again and silently overturned a ruling that landed hours ago. The `lanes_doc` fixture cannot catch it (it feeds `producer_commented` directly), so this is added as a conjunct on `is_leak_candidate` with the ruling cited, plus an assertion over the registry below. It cannot reintroduce the defect this predicate exists to fix: that defect was `human:*` labels parked in a real lane, and the classifier arm is what excludes those. THE THREE PIECES OF COVERAGE THE HUMAN CALLED FOR, so none of the above is re-verified by hand at the next merge: 1. `every_lane_is_reachable_from_some_label_set` — one witness per `Lane`, driven off a new `Lane::ALL`, so an arm going unreachable behind a precedence change is a failure and a lane added later is covered on the day it is added. 2. `DELETED_LABELS` + `a_deleted_label_models_nothing_anywhere` — one property over a registry, replacing the two bespoke per-deletion tests #219 and #221 each shipped. For every deleted label: the ordinary unmodelled fallthrough, dominates nothing, occupies no lane cell (checked across `Lane::ALL`), and no `counts` key names it. The next deletion adds a row of DATA. 3. `lanes_doc_emits_every_state_with_the_right_members` now derives every total from per-row expectations instead of literals. That assertion is the one that read `3` on two branches and was wrong at `4` once both landed, with nothing pointing at it; an expectation beside its input cannot go stale that way. nm.md, the README and the doc comments now state the post-deletion truth: a RETIRED label stays bucketed and never leaks; a deleted `ai:*` label is absorbed as un-vetted; a deleted `human:*` label DOES leak, because the vetter's sweep is `ai:*`-scoped and nothing else claims it. The classifier rule nm.md tells the human to check is now its three real clauses rather than the `ai:*`-only paraphrase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…) into /nd Conflicts, and how each resolved: - `.claude-plugin/marketplace.json` + `plugins/human-fsm/.claude-plugin/plugin.json` — both sides bumped the plugin. OURS: 0.14.0 stays, distinct from and above main's 0.12.0 (#224) and the 0.13.0 #227 still claims, and the description keeps the third read. - `plugins/human-fsm/README.md` — the whole command table. MAIN's table taken wholesale, because #224 rewrote the `/design` row and resolving toward ours would restore the `human:design` / `--park` prose #219 deleted; the `/nd` row is spliced back into it. Both intents, main's row verbatim. Everything else auto-merged. Main's deletions stand: nothing here reintroduces `human:design` or `ai:blocked-deploy` in any spelling — every remaining mention in the tree is main's own assertion that the deleted state is inert. nd.md needed no correction against merged main: it describes the post-#219 exit (`ai:reject` with the answer as the trusted work order, one call at the head anchor, no parked spelling), and `human-rule … design --rework` now does exactly that. What was aspirational when written is now what the code does. Coverage rather than hand-verification, per the standing call — two hand-written totals near the merged region are now DERIVED from their fixtures: - the withheld-overflow expectation is `raised - ND_MAX_WITHHELD` off a fixture sized from the cap, not a literal `2`; - the row and envelope digit reserves count their numeric fields from the emitted document (`numeric_fields`) instead of a number typed in a comment. That is the exact shape that survives review and breaks on a merge: a count key added on one branch and a reserve typed on another are each correct alone and conflict with nothing when both land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… drain `migrate-needs-work` was the #133 one-shot: move every open PR still carrying the RETIRED PR-side `human:needs-work` onto `ai:needs-work`. It has run. Across `rainlanguage` + `cyclofinance` + `S01-Issuer` the open-PR population on that label is now ZERO (the two remaining subjects are closed PRs), which is the same residue-zero bar #225 held `migrate-blocked-deploy` to. Per the standing ruling on #221 a migration is an execution vehicle rather than permanent machinery, so the verb comes out with the state it drained — exactly as `migrate-blocked-deploy` (#225) and `migrate-design` (#224) did, each leaving only a test that the subcommand no longer parses. There is no separate `migrate-reject`: this branch had renamed that verb to `migrate-needs-work`, so both spellings go with this one deletion. Done compiler-first — the `Cmd` variant, its dispatch arm, the plan/mode machinery and its tests, then `RETIRED_HUMAN_NEEDS_WORK_LABEL` itself, following every rustc error rather than re-literalising anything to keep the tree compiling. That cascade is larger than the subcommand, because the retired label was the last thing holding the PR-side sacred-label machinery up: - `PR_SACRED_LABELS` had one entry, so it and `has_human_override` are gone. A PR's human-sacredness is now exactly what #133/#219 said it should be: a native review, or a `👤 human` ruling pinned to the current head. No label. - `PrSignals.human_parked` and `next_action`'s parking arm — #225's list names this one explicitly. Parking is now a modeled `ai:*` state, not a namespace. - `STATE_HUMAN_NEEDS_WORK`, its `classify_lane` arm, its daily-review section, and `Lane::HumanDecisions` — the lane held that one state, so it drained to nothing and came out with it. - `DesignHit::HumanRuled` and `/nd`'s `humanRuled` count, whose only producer was `has_human_override`. `aiDesign`'s stated partition loses that term. - The worklist row's `humanOverride` key, for the same reason. Nothing outside the binary read it. `human:needs-work` is NOT added to `DELETED_STATE_LABELS`, deliberately. That registry claims no transition can write the string again, and on an ISSUE that is false: `human-rule-issue needs-work` still writes it and 21 open issues carry it today. Only the PR-side state was removed, so the lane-arithmetic test names the string locally and says why. What the compiler could NOT see, and the suite caught: fourteen tests built their human-sacred witness from the string literal `"human:needs-work"` rather than the constant, so they compiled clean and then failed — the verdict/scope/ lens/convention gates, the MCP refusal, `producer_state_plan`, the state-load fixture's 36-PR cohort, and the golden descriptor and lane rows. Each now uses a form that is still a human decision (a native review), or asserts the new truth. The last grep sweep then found what neither reached: prose in README.md, CLAUDE.md, `keep-open.md` and `nm.md`, plus the subcommand's row in CLAUDE.md's table — seven documentation sites with no compiler or test coverage at all. Counts re-derived from the merged table rather than carried across: `STATE_DESCRIPTORS` 12 -> 11, `REVIEW_LANE_SECTIONS` 8 -> 7, `Lane::ALL` 6 -> 5. Gates: `cargo test` 1047+3+9+47+8+7+4 passed 0 failed; CI-exact `rainix-rs-static` at rainix 53e96a7 with the changed file touched first; `pre-commit run --all-files`; `nix build .#pr-review-report`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #221
What
Two things, in order — and the order is the point.
1. The migration ran, once. The 2026-08-06 ruling in #221 (verbatim: "migrate all the blocked on deploy back to rejected so it can be reworked to fit in the new deployment paradigm") was executed against live GitHub from this branch's earlier build, over the live population of 13 open PRs. Each got
ai:reject, a sha-pinned👤 humanruling comment recording the ruling it executes, and a trustedRework notecarrying the uniform split-release-lifecycle work order. The transcripts below are that once-off's permanent record; the residue is zero and stays zero.2. The state is deleted. Per the ruling revision recorded at #221 (comment) (verbatim: "a subcommand? i just want this migrated as a once off so we can delete the state entirely"), the tree that merges carries neither the subcommand nor the state:
migrate-blocked-deploysubcommand is gone in full —Cmdvariant, dispatch arm, plan/steps/search machinery, its tests. It ran once; machinery for an empty population is dead weight. (The code that ran is in this branch's history — commit6caefc1— which is what the transcripts were produced by.)ai:blocked-deployis deleted from the FSM compiler-first:RETIRED_BLOCKED_DEPLOY_LABELwas deleted and rustc enumerated every consumer — theclassify_lanearm, theblockedDeploycounts emission, the human-queue display section, thelabel_metarow, thenext_actionparking arm. Grep was used only as the final prose sweep, never to derive the inventory.What the deletion means behaviorally, and what the new tests pin: the label no longer classifies — it is unmodelled, so a PR wearing it falls through to
un-vetted: the vetter absorbs it and the verdict that judges it strips the dead label. (It is not a leak, and an earlier revision of this PR wrongly said so:human_queue_modebuckets everyai:*-labelled PR viaai_state_labeland leak-detects only theunlabeledremainder, soproducer_commented— the only route to theLeakarm — is alwaysfalsefor a labelled PR. The tests now feed the input the pipeline actually constructs.) Nothing parks or blocks on it (next_actionreturns the ordinary CI classification, and a deploy-shaped signal still routes toFlagMigration); andcountscarries noblockedDeploykey at all — absent, not zero, because a key held at zero is a dashboard box for a state the machine does not have.#162's live rule is untouched and still stated: a deploy-shaped block (red prod-pin, legacy redeploy marker) is evidence the repo has not migrated to the split release lifecycle, expressed as a typed
ai:blocked-onagainst that migration.flag-blocked-deploystill parses and still refuses, teaching exactly that replacement.README, the FSM diagram note and the campaign prompt drop the residue/migration prose accordingly. Comments describe current behavior only.
Sibling coordination
At the time of writing, #223 (stateDescriptors), #224, #226 and #227 are all still open, so this PR merges first.
mainwas merged in (never rebased) to pick up the current head.PR 223 must delete the state on its merge-main, not relocate it. At
fb5ad79it does not merely add a descriptor row — it re-establishesai:blocked-deployacross nine sites, and following a "drop the row" instruction literally (re-literalising the const so it compiles) lands resurrection mutant #2 from the table above, green. The required move is: deleteRETIRED_BLOCKED_DEPLOY_LABELoutright — do not re-literalise it to"ai:blocked-deploy"— and let rustc enumerate the rest, which is the same compiler-first method this PR used. The sites, so none is missed:const RETIRED_BLOCKED_DEPLOY_LABEL: &str = STATE_BLOCKED_DEPLOY.key;— the redefinition. Delete the constant, do not re-point it.const STATE_BLOCKED_DEPLOY: StateDescriptor { … }— the whole descriptor, includinghist: Some("blockedDeploy"),label: Some("ai:blocked-deploy (retired #162)"),occupancy: StateOccupancy::Lane(Lane::ProducerBlocked)andemit: Emit::WhileOccupied("blockedDeploy").&STATE_BLOCKED_DEPLOYinSTATE_DESCRIPTORS— and the array length drops from14to13."blockedDeploy": buckets.get(RETIRED_BLOCKED_DEPLOY_LABEL)…in the descriptor-drivencountsblock (this PR deletes the key entirely — absent, not zero).RETIRED_BLOCKED_DEPLOY_LABELin the state-label list iterated alongsideRETIRED_HUMAN_REJECT_LABEL/RETIRED_STATE_LABEL/STATE_BLOCKED_ON.key/STATE_READY.key.[RETIRED_BLOCKED_DEPLOY_LABEL]amongclassify_lane's label constants."key": "ai:blocked-deploy"/"hist": "blockedDeploy"/"label": …descriptor-JSON test fixture, and the"ai:blocked-deploy"entry in the expected key list.residue["blockedDeploy"] = 13fixture and the test that asserts the residue row is emitted while occupied (.find(|d| d["key"] == "ai:blocked-deploy")).The residue that fixture describes is zero on live GitHub and the label definitions are deleted, so nothing there can be occupied again.
Label-definition sweep
Enumerated across all 134 non-archived repos in
rainlanguage+cyclofinance+S01-Issuer; 6 defined the label. Open PRs carrying it at sweep time: 0.Gates
cargo test(full suite, via nix): 1002 passed, 0 failed in the main binary; 78 more across the other targets, all green.github:rainlanguage/rainix/53e96a7…#rust-shell, rustc 1.94.0), changed file touched first:cargo fmt --all -- --check→FMT-OK;cargo clippy --all-targets --all-features -- -D warnings -D clippy::all→CLIPPY-OK.nix build .#pr-review-report --print-build-logs→ exit 0.Adversarial mutation pass (re-scoped to the deletion)
Every mutant resurrects the deleted state in some way — that is what the deletion's tests have to be able to kill. Each was applied to the committed tree, judged by the FULL
cargo testsuite, then restored from git.classify_lanearm returns(ProducerBlocked, "ai:blocked-deploy")againfsm_completeness_tests::lanes_doc_emits_every_state_with_the_right_members,infra_down_tests::the_deleted_blocked_deploy_label_classifies_as_nothing,subject_ref_tests::the_deleted_blocked_deploy_state_emits_no_count_keyblockedDeploycounts key re-emittedsubject_ref_tests::the_deleted_blocked_deploy_state_emits_no_count_keynext_actionparks on the label againworklist_tests::modeled_state_label_short_circuits_to_parkedlabel_metarow restored (label re-creatable org-wide)b4ffc8c), re-run KILLED —infra_down_tests::the_retired_labels_are_no_longer_destinationsPRODUCER_STATE_LABELSinfra_down_tests::the_retired_labels_are_no_longer_destinations,worklist_tests::modeled_state_label_short_circuits_to_parkedstate_nounanswers for it (a transition could name it)infra_down_tests::the_retired_labels_are_no_longer_destinations6/6 killed after the one strengthening; tree restored clean (
git status --porcelainempty), baseline suite green at 1002 passed / 0 failed.The survivor was a real gap and is worth naming:
label_metais whatEnsureLabelhands togh label create --force, so a surviving row is exactly what would let the next caller that named the string re-create the six label definitions this PR deleted. The strengthened assertion compares against an arbitrary unknown label ("no row of its own") rather than literal default bytes, and keeps the deliberate contrast that retired-but-occupiedai:blocked-infrastill HAS its row.QA
infra_down_tests::the_deleted_blocked_deploy_label_classifies_as_nothing,subject_ref_tests::the_deleted_blocked_deploy_state_emits_no_count_key,infra_down_tests::the_retired_labels_are_no_longer_destinations(extended),worklist_tests::modeled_state_label_short_circuits_to_parked(extended),fsm_completeness_tests::lanes_doc_emits_every_state_with_the_right_members(extended) — each fails on base: on base the label DOES classify as producer-blocked (rather than falling through to un-vetted), DOES emitcounts.blockedDeploy, DOES park innext_action, and DOES carry alabel_metarow, so every one of these assertions is the negation of base behavior. Verified per-assertion by the mutation table above, which restores each base behavior individually and confirms the kill.Dry-run transcript (live GitHub, report mode)
Live execution transcript (
--apply)Post-run verification
🤖 Generated with Claude Code