Retire ai:blocked-deploy: deploys never block merges under the split lifecycle - #168
Conversation
…lifecycle flag-blocked-deploy is removed from the producer's vocabulary the way ai:relink's writer was (#135/#139): the subcommand still parses but refuses, naming the replacement (flag-blocked-on --blocked-by <the repo's migration ref>), and the label constant survives as RETIRED for residue accounting only. The queue emission keeps the blockedDeploy key while the count can be non-zero; the 13 live PRs stay visible for the human's eyes-on triage, parked (unlike the #108 residue) so no automatic pass re-derives them. The deploy-gate reading is re-pointed end to end: NextAction::Deploy becomes FlagMigration, DeployGate becomes LegacyDeploySignal (none/repo-not-migrated, head-blind, never a merge gate), and the prompts stop teaching the deploy choreography — a deploy-shaped block is a repo-migration dependency, flagged ai:blocked-on with the migration issue/PR as its typed dep, filed if none exists. Closes #162 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
QA evidence for this PR, run on the branch as pushed. Full suite ( Static gate, CI's exact spelling: Mutation battery — 12 mutants, applied one at a time to the committed baseline, each restored before the next; every one killed by the named test, tree clean afterwards:
Clearance interplay with #164 (a blocked-deploy PR is not the vetter's to clear): the state-load selects the clearance path on the exact No 🤖 Generated with Claude Code |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
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 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 (6)
WalkthroughThe pipeline retires deploy-before-merge blocking. It reports repository migration evidence through ChangesDeploy lifecycle migration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Worklist
participant LegacyDeploySignal
participant FSM
participant MigrationIssue
Worklist->>LegacyDeploySignal: derive repository migration evidence
LegacyDeploySignal->>FSM: report repo-not-migrated
FSM->>MigrationIssue: route flag-migration with typed dependency
MigrationIssue-->>FSM: current-head confirmation suppresses routing
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@campaign-prompt.txt`:
- Around line 69-70: Update the producer prompt’s transition guidance to remove
raw GitHub and git mutation commands, including gh pr/issue operations and git
push. Require all GitHub input, output, and state transitions to use the tested
pr-review-report subcommands, while preserving the existing prohibitions on
merge, close, deploy, force-push, and reset operations.
- Line 46: Make lifecycle-migration issue resolution atomic in the migration
path around the “find or FILE it” step before flag-blocked-on: use a stable
repository-specific marker with an idempotent create-or-find operation, or
serialize lookup and creation, so concurrent workers reuse one migration
issue/PR. Ensure all dependency flags reference the same canonical migration
issue and preserve the existing behavior when an open migration issue already
exists.
In `@pr-review-report-rs/src/main.rs`:
- Line 7546: Update the dominated closure in human_queue_mode so its
producer-label exclusion also treats RETIRED_BLOCKED_DEPLOY_LABEL as dominated,
matching the explicit handling already used by next_action alongside
PRODUCER_STATE_LABELS. Preserve the existing behavior for all labels in
PRODUCER_STATE_LABELS and ensure PRs carrying ai:ready with the retired
blocked-deploy residue do not trigger the extra ready_vetted_at_head fetch.
🪄 Autofix (Beta)
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: ab03a7da-f77a-4c3d-9d6e-f51a5a5c81f3
📒 Files selected for processing (7)
.claude-plugin/marketplace.jsonREADME.mdcampaign-prompt.txtplugins/human-fsm/.claude-plugin/plugin.jsonplugins/human-fsm/commands/nr.mdpr-review-report-rs/src/main.rsreview-prompt.txt
Retiring `ai:blocked-deploy` took it out of `PRODUCER_STATE_LABELS`, and `human_queue_mode`'s `dominated` closure derived "this PR is excluded from the ai:ready verdict-currency fetch" from that same array — so a PR carrying the residue label alongside a stale `ai:ready` started paying a `gh pr view` whose result nothing reads. `classify_lane` returns `ProducerBlocked` at the residue check before the `ai:ready` branch ever consults `ready_vetted_at_head`, and `lanes_doc` is that value's only reader, so the lane, the counts and the emitted JSON were all unaffected: the regression was one wasted fetch per residue PR. The closure was a hand-maintained mirror of `classify_lane`'s precedence order, and it had already drifted the same way once — `ai:blocked-infra` left `PRODUCER_STATE_LABELS` at #108 while staying ahead of `ai:ready`, and nothing was added here for it either. So the mirror goes rather than gaining a third entry: `needs_verdict_currency` asks `classify_lane` whether the answer moves the PR at all, comparing `Some(false)` (what a fetch finding a stale verdict yields) against `None` (literally what the lookup yields when no fetch was made). It names no label, so every future retirement carries itself, and it subsumes the separate `ai:ready` presence check the loop carried. `verdict_currency_is_fetched_only_where_it_can_change_the_lane` asserts the fetch is made for `ai:ready` and for `ai:ready` + `ai:design` (which does not dominate), and refused for every state returning ahead of that branch — `human:design`, `human:close-candidate`, `human:reject`, `ai:blocked-deploy`, `ai:blocked-on`, `ai:blocked-infra` — each paired with the assertion that skipping the fetch leaves `classify_lane`'s answer identical. Mutating the function back to the `PRODUCER_STATE_LABELS`-derived form fails it on `ai:blocked-deploy` and `ai:blocked-infra`; the rest of the suite stays green under that mutant, which is why this needed a test of its own. Co-Authored-By: Claude <noreply@anthropic.com>
Three conflicts, all resolved as unions rather than by picking a side: marketplace.json / plugin.json: description text only. main's is a superset (it documents the close-candidate reader and /ncc); this branch had no stake in either sentence, so main's wins. campaign-prompt.txt step 3b: main added the 3-item run budget while this branch renamed the deploy nextAction to flag-migration. Kept main's budget sentence carrying this branch's rename. campaign-prompt.txt steps 6-7: this branch retires the producer deploy path (#162), which is its whole purpose, so its side stands — but it was written before main replaced gh pr create with mcp__fsm__open_pr, so that change is grafted into the surviving step 7. cargo test: 789 passed, 0 failed. Co-Authored-By: Claude <noreply@anthropic.com>
|
🤖 ai:producer Producer note: conflicts with
One thing for you, not a blocker. Both sides independently bumped the |
Both this branch and main independently bumped human-fsm to 0.7.0, so the merge took that line without a conflict and the version stopped moving relative to main — which is exactly what the gate checks. main shipped 0.7.0 with different content, so this branch's release is 0.8.0. plugin-version-lockstep: ok human-fsm 0.8.0. Co-Authored-By: Claude <noreply@anthropic.com>
|
🤖 ai:producer Correction to my previous note: the version slot was not yours to decide after all — CI settled it. The
Both sides had independently bumped to 0.7.0, so the merge took that line cleanly and the version stopped moving relative to |
…y has `legacy_deploy_signal`'s doc claimed the human's shape signal and the producer's `next_action` "can never be answered differently". False since the #162 re-point: `next_action` gates on three inputs (`has_deploy_trigger && !deploy_done_at_head`), this signal reads two. On a head carrying a trusted deploy-confirmed note they DO answer differently — the `/nr` row says `repo-not-migrated` while `next_action` declines to flag. The BEHAVIOUR is right: repo shape and this-run action are different questions, and head-blindness is correct for a signal that reports shape. Only the claim is wrong, so only the claim moves — both sides read the same two SHAPE signals and can never disagree about whether the repo is legacy-shaped; they diverge on exactly one further input, deliberately. Same drift fixed in `worklist_row`'s marker comment, which inherited the identical parity claim. Two further comment claims corrected while auditing the re-point: - `next_action`'s "so it leads" omitted the two gates that actually lead (human override, then the modeled-state park) — the deploy-shaped signal leads the CI-DERIVED cases, not the whole classifier. - `deploy_confirmed_at_head`'s "the SHORT sha still counts" is true only of a 12-char prefix; the match is fixed at 12, so a 7-char abbreviation does not count. Said so. And the relationship is now PINNED, not just argued: a new test runs both sides over the same fixture matrix — both spellings of the shape evidence, both ways of not being it — asserts they agree on repo shape every time, and asserts the single deliberate divergence at a deploy-confirmed head. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ol-push rewrite Merge base was 92 commits behind main, 74 of them touching main.rs. Seven conflict hunks, every one resolved as the UNION — main's edits survive and this PR's substance is unchanged. pr-review-report-rs/src/main.rs (1 hunk, the `next_ready` MCP tool): main added `"narrows": "limit"`, this PR rewrote the description to the retirement's `legacyDeploySignal` wording. Kept both. .claude-plugin/marketplace.json + plugins/human-fsm/.claude-plugin/plugin.json: version bumps on both sides (0.8.0 here, 0.9.2 on main). Resolved to 0.9.3 in BOTH files — strictly greater than main's, and the two stay in lockstep for `plugin-version-lockstep`, while the version-hygiene `bump` gate sees a change against a 0.9.2 base. campaign-prompt.txt (4 hunks): - steps 1/2/2z — main's rewrite wins whole (the `preflight`-asserted environment, `state-load`'s grouped read, `fleet.approved`). Only the worklist vocabulary is this PR's: `deploy` becomes `flag-migration`. - 3b (i)'s two bullets — main's transient-flake bullet (`mcp__fsm__push`), this PR's infra bullet (a red prod-pin is the MIGRATION case, not the DEPLOY one). - 3b (iii)/(iv) + 3c — main's push-is-a-tool (iii) and its rewritten 3c (which now routes off `worklist` rows rather than a second path list); this PR's (iv) MIGRATION replaces main's (iv) DEPLOY. - steps 6/7 — main's clone-release and push/open_pr tool language, with this PR's deploy-retirement sentences spliced in: no redeploy marker, no producer deploy in any form, and the lifecycle-migration `gh issue create` carve-out. Two things the textual merge got wrong and a reader would not see: - `human_queue_doc` grew from 10 to 13 parameters on main (#165's `Option<&[OpenIssue]>`, plus `archived_prs` and `now_ms`). This PR's blocked-deploy kept-while-nonzero test still called the 10-arg form and the test binary did not compile. Updated to the real signature: no open-issue population (`None` — the test asserts a count, not an age), no archived PRs, and the module's `DOC_NOW_MS`. - three step references still counted a producer deploy as a work item after this PR removed the deploy path: the run budget's "a deploy you dispatch", step 6(c)'s "3b(iv) deploy dispatched", and step 9's "rework/conflict/deploy work". A prompt that both forbids every deploy and budgets for one is a contradiction the retirement has to close, so each now names the migration flag that replaced it. Verified on the merged tree, not assumed: no surviving `deploy_gate` / `DeployGate` / `NextAction::Deploy` / `deployGate`; `PRODUCER_STATE_LABELS` is still `[&str; 2]`; `next_action` still orders human-override → modeled-state park (including the retired label) → `FlagMigration` → the CI classifier; `classify_lane` still files the residue producer-blocked; `blockedDeploy` is still emitted kept-while-nonzero; `VET_PROTOCOL` stays at main's 4 (this PR does not bump it); `PrSignals` gained no field needing population. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed true Third instance of the parity claim the code comments carried: the README's `legacyDeploySignal` bullet said the producer's "flags this blocked on the migration" and the human's "sees a repo that needs migrating" "cannot be answered differently". They can — on a head whose deploy the old choreography confirmed, `next_action` declines to flag while the row still reports `repo-not-migrated`, because only the producer reads `deploy_confirmed_at_head`. The row is right and the sentence was wrong, so the sentence moves: the two share the SAME two shape predicates and can never disagree about whether the repo is legacy-shaped, and the one input they do not share is named, along with why the row is head-blind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review finding fixed, and
|
| Site | Claim | Verdict |
|---|---|---|
legacy_deploy_signal |
"can never be answered differently" | FALSE — fixed (above) |
worklist_row's marker comment |
same parity claim, second copy: "the producer's 'flag this blocked on the migration' and the human's 'this repo needs migrating' cannot disagree" | FALSE — fixed. Now: they cannot disagree about whether the repo is legacy-SHAPED, and deployDoneAtHead is named as the producer's own extra input |
README legacyDeploySignal bullet |
same parity claim, third copy | FALSE — fixed in 8793a16. This one is the human-facing copy, and the wrap across markdown lines is why a grep for the sentence missed it first time |
next_action's doc |
"a deploy-shaped signal … so it leads" | FALSE as an ordering claim — fixed. Human override and the modeled-state park both precede it; the deploy-shaped signal leads the CI-DERIVED cases. The sentence was rewritten by this PR, so it is this PR's to get right |
deploy_confirmed_at_head |
"a note that embedded the SHORT sha still counts" | Imprecise — tightened. head_short is head[..12], so a 12-char prefix counts and a 7-char abbreviation does not. Now says so; the existing test already pinned 12 |
REDEPLOY_MARKER |
"no new PR is flagged with it (the producer prompt no longer teaches it)" | Holds. campaign-prompt.txt names the marker only as a signal to DETECT and route; nothing instructs writing one |
REDEPLOY_MARKER |
"it gates nothing" | Holds on the reading its own em-dash gives it ("no merge waits on a deploy"). It does still route the producer to FlagMigration; that is an action, not a merge gate |
REDEPLOY_MARKER |
"reads those two and never title" |
Holds — body + trusted_comments, pinned by the_legacy_deploy_signal_is_read_from_the_body_not_the_title |
requires_redeploy |
"untrusted authors are filtered by trusted_comments" |
Holds, pinned by the spoofed-author case in the same test |
deploy_pin_red |
"can only fire while the PR is red" | Holds — ci == Ci::Red && guards it |
deploy_confirmed_at_head |
"now suppresses the MIGRATION flag" / "an empty head can never read as confirmed" | Holds — early return false on an empty head |
LegacyDeploySignal enum |
"the signal never blocks anything" | Holds — the only consumer is the /nr row's JSON field |
NextAction::FlagMigration |
"no code fix can green a prod-pin there" | Holds — no code path deploys |
RETIRED_BLOCKED_DEPLOY_LABEL |
"residue STILL PARKS, unlike ai:blocked-infra" |
Holds — next_action consults it, RETIRED_STATE_LABEL is not consulted |
RETIRED_BLOCKED_DEPLOY_LABEL |
"classify_lane keeps it visible (producer-blocked)" |
Holds, pinned |
state_noun |
"ai:blocked-deploy deliberately absent; no transition writes that state" |
Holds — no arm, and flag-blocked-deploy refuses |
worklist_row's other marker keys |
requiresRedeploy / deployDoneAtHead / uiTouch descriptions |
Hold |
next_ready_row's legacyDeploySignal comment |
"never a merge gate" | Holds |
MCP next_ready tool description |
"never a merge gate — #162" | Holds |
/nr command doc (plugins/human-fsm/commands/nr.md) |
the legacy-signal section | Holds — it makes no parity claim |
3. The invariant is now pinned, not only argued
the_shape_signal_and_the_migration_flag_diverge_only_at_a_confirmed_head runs
both sides over the same fixture matrix — both spellings of the shape evidence
(the marker, a red prod-pin) and both ways of not being it (a plain green PR, a
red PR whose red is not a pin) — asserts the two agree on repo shape on every
one, and then asserts the single deliberate divergence at a deploy-confirmed
head. A future edit that gives one side a third input breaks it.
Head-blindness alone was already pinned
(a_deploy_confirmation_does_not_erase_the_repo_shape_signal); what was not
pinned anywhere was the two sides agreeing on the same fixture.
4. The merge — seven conflicts, all resolved as unions
Merge base was 92 commits behind main, 74 of them touching main.rs.
pr-review-report-rs/src/main.rs (1) — the next_ready MCP tool entry.
main added "narrows": "limit"; this PR rewrote the description to the
legacyDeploySignal wording. Kept both.
.claude-plugin/marketplace.json + plugins/human-fsm/.claude-plugin/plugin.json (2)
— version bumps on both sides (0.8.0 here, 0.9.2 on main). Resolved to
0.9.3 in both, so plugin-version-lockstep sees them agree and the
version-hygiene bump gate sees a change against a 0.9.2 base.
campaign-prompt.txt (4)
- steps 1/2/2z —
main's rewrite whole (thepreflight-asserted environment,
state-load's grouped read,fleet.approved). The only thing taken from this
side is the worklist vocabulary:deploy→flag-migration. - 3b (i)'s two bullets —
main's transient-flake bullet (nowmcp__fsm__push),
this PR's infra bullet (a red prod-pin is the MIGRATION case, not the DEPLOY
one). - 3b (iii)/(iv) + 3c —
main's push-is-a-tool (iii) and its rewritten 3c (which
routes offworklistrows instead of a second path list); this PR's (iv)
MIGRATION in place ofmain's (iv) DEPLOY. - steps 6/7 —
main's clone-release and push/open_pr tool language, with this
PR's deploy-retirement sentences spliced in: no redeploy marker, no producer
deploy in any form, and the lifecycle-migrationgh issue createcarve-out.
Nothing had to be dropped: no pair of changes contradicted.
5. Two things the textual merge got wrong
Both found by reading and building the merged tree, not by trusting a clean
textual merge.
human_queue_docgrew from 10 to 13 parameters onmain(refresh-human-queue: emit uncovered-issues age stats (median/oldest days) beside counts — recording half of rain-org-health#140 #165's
Option<&[OpenIssue]>, plusarchived_prsandnow_ms). This PR's
the_retired_blocked_deploy_count_keeps_its_key_and_counts_the_residuestill
called the 10-arg form, and the test binary did not compile. Updated to the
real signature —Nonefor the open-issue population (the test asserts a
count, not an age), no archived PRs, and the module'sDOC_NOW_MS.- Three step references still counted a producer deploy as a work item after
this PR removed the deploy path: the run budget's "a deploy you dispatch",
step 6(c)'s "3b(iv) deploy dispatched", and step 9's
"rework/conflict/deploy work". A prompt that both forbids every deploy (step 7)
and budgets for one is a contradiction the retirement has to close, so each now
names the migration flag that replaced it.
6. Semantic checks on the merged tree
- Zero surviving
deploy_gate/DeployGate/NextAction::Deploy/
"deployGate"anywhere in the repo. PRODUCER_STATE_LABELSis[&str; 2]; the drift-catching
len() == 2assertion still holds.next_action's order survives: human override → modeled-state park (including
RETIRED_BLOCKED_DEPLOY_LABEL) →FlagMigration→ the CI classifier.classify_lanestill files the residue producer-blocked, and ai:blocked-on sits with the vetter: typed dependency refs, automated clearance on dep merge, human polling removed #164's
clearance still never selects it.blockedDeployis still emitted kept-while-nonzero, counting via
RETIRED_BLOCKED_DEPLOY_LABEL.VET_PROTOCOLstays atmain's4— not bumped here.PrSignalsgained no field onmain, so nothing new to populate in
worklist_row.- Withhold archived-repo rows from every org-wide enumeration, with a stated count #209's
every_org_wide_enumeration_withholds_archived_repospasses: nothing
here introduces or moves an org-wide enumeration. - The 13 live
ai:blocked-deployPRs are untouched — that is the ruling.
7. Verification
Everything through nix, from the merged tree.
cargo build— clean.cargo test— 1049 passed, 0 failed, 0 ignored (971 + 3 + 9 + 47 + 8 + 7 + 4
across the seven binaries).rainix-rs-static(rustfmt + clippy, CI's exact invocation at rainix
53e96a7), aftertouchingmain.rsso a warm target could not mask a lint
— clean, zero warnings.pre-commit run --all-files— 11 hooks, all Passed (deadnix, denofmt, nil,
nixfmt, no-consumer-prettier, prettier-rainix, rustfmt, shellcheck, statix,
taplo, yamlfmt).
|
Reviewed 8793a16: ready Verified before merge: 20 SUCCESS / 1 skipped, MERGEABLE/CLEAN, 0 unresolved threads, 1049 tests, clippy and pre-commit clean. I read the diff, the The retirement is the human's ruling made structural: under the split lifecycle no merge waits on a deploy, so a PR that would earn The re-point is the substance, not the deletion. Every deploy-gate reader is kept and re-aimed at repo SHAPE — It composes with #164 rather than colliding: the residue keeps On the send-back finding: the false parity claim existed in THREE places, not the one I named — the The added test pins what nothing pinned: head-blindness was already covered, the two sides AGREEING was not. It runs both functions over both spellings of the shape evidence and both ways of not being it, asserting agreement each time before asserting the single divergence — and supplies The base merge caught two things reading alone would not: One caveat recorded rather than hidden: CodeRabbit's green check reads |
Closes #162
The ruling, honored
Under the split lifecycle no merge waits on a deploy anywhere — deploy repos freeze per-tag records and publish on
rainix-tag-release, the on-chain deploy is a decoupled manual dispatch (rainlanguage/rainix#282), library repos never deploy. Soai:blocked-deploynames a wait that no longer exists: a PR that would earn it is evidence of exactly one thing — its repo has not migrated. This PR makes that the machine's reading end to end, composing with #164's typed---blocked-byclearance: blocked-deploy collapses into the ONE blocked state whose clearance the vetter already automates.The retirement, by the established precedent
flag-blocked-deployis retired the wayai:relink's writer was (#135/#139) and the way every retirement here has been shaped (#108/#133):--dry-runincluded, no fetch, no write) with a message naming the replacement verbatim —pr-review-report flag-blocked-on <owner/repo> <n> "<why>" --blocked-by <owner/repo#n of the repo's migration issue/PR>, filing the migration issue first if none exists. The subcommand deliberately still parses (unlikeflag-blocked-infra, which is gone from the surface): a clap unknown-subcommand error names neither the why nor the repair, and the refusal is where the old spelling learns the new move.RETIRED_BLOCKED_DEPLOY_LABEL) for residue accounting only, on the kept-while-nonzero contract every retirement has used.PRODUCER_STATE_LABELSis now exactly["ai:design", "ai:blocked-on"].gh issue create— the one sanctioned issue creation — shaped as the audit skill's lifecycle-split finding;flag-blocked-onwith the migration as the typed dep), and the prompt states "There is no blocked-deploy state and no producer deploy any more".Residue plan (the 13 live PRs) — eyes-on, never auto-migrated
Per the issue's ruling this PR migrates nothing automatically:
blockedDeploykey (counts down to 0, never absent) andhuman-queueshows the bucket asBLOCKED-DEPLOY (RETIRED #162) — re-flag blocked-on the repo's migration, or unblock;classify_lanekeeps the residue producer-blocked — deliberately NOT vet-lifecycle, because ai:blocked-on sits with the vetter: typed dependency refs, automated clearance on dep merge, human polling removed #164's clearance reads exactlyai:blocked-ontyped refs, which this residue has neither of (pinned by test: a blocked-deploy PR is never selected by the clearance, and a residueBlocked-deploy:note never reads as a live blocked-on flag);next_actionparks the residue (contrast Producer should exit and log an error when infra is down — and blocked-infra should not be a state #108's blocked-infra, which deliberately un-parks): a producer that re-derived these PRs from CI would perform exactly the auto-migration the ruling forbids. The exit is the human pass that re-flags each PRai:blocked-on --blocked-by <its repo's migration>or unblocks it outright where the repo has already migrated.The deploy-gate readers, enumerated (issue item 5)
Every place the binary or prompts read the deploy gate, and what happened to each:
REDEPLOY_MARKER("REQUIRES redeploy at land"body marker)requires_redeploy()— the substring match over body + trusted commentsdeploy_pin_red()— red prod-pin /testProdDeploy*check namesdeploy_confirmed_at_head()next_action()—NextAction::DeployNextAction::FlagMigration("flag-migration"): the producer's route is the blocked-on-migration flag, never deploy choreographyaction_rank("deploy") => 0"flag-migration" => 0(new test pins the rank so a one-sided rename cannot silently sink it)DeployGateenum +deploy_gate()LegacyDeploySignal/legacy_deploy_signal():none|repo-not-migrated, deliberately head-BLIND (repo shape, not choreography state), never a gatenext_ready_row"deployGate"JSON key (human MCP row)"legacyDeploySignal"— a key named "gate" whose values stopped gating would teach the old modelnext_readytool description ("any deploy-before-merge gate")worklist_rowmarkersrequiresRedeploy/deployDoneAtHeadCmd::Deploy/deploy_mode(themanual-sol-artifactsdispatch wrapper)next_action) and its docs now say so. Not ruled by #162, so not removed; flagging here for visibilitydeploy→flag-migration), step 2z RED/GREEN bullets + WAITING-DEPLOY report vocabulary, step 3b intro + (i) env bullet + (iv) DEPLOY→MIGRATION, COMMUNICATION routing table, step 6 conventions (no marker on new PRs), step 7 scope (producer deploys nothing;gh issue createsanctioned solely for a missing migration issue)bdeploystate + its three edges +producer deployself-edge),next_readysection's deploy-gate paragraph + bullet, lanes doc, modeled-states paragraphplugins/human-fsm/commands/nr.md— the deploy-before-merge gate bullet + the "not a plain merge" presentation paragraphComposition with #164
ai:blocked-onlabel; the retired residue is never touched by it (different label, no typed refs) — pinned inblocked_deploy_residue_is_producer_blocked_not_the_vetters_to_clearand by the existinglive_blocked_on_flagfixture where aBlocked-deploy:note never matches.No
VET_PROTOCOLbumpVerified: what vetting MEANS is unchanged — no new mandatory gate, no verdict-vocabulary change; the vetter's only edit is the re-pointed incidental-red example.
VET_PROTOCOLstays 4.Dash follow-up (out of scope)
The rain-org-health follow-up — retire the BLOCKED-DEPLOY box and fold
blockedDeployhistory, the #147/#151 shape — is deliberately NOT in this PR. It is gated on the residue count reaching zero (the kept-while-nonzero contract this PR preserves), and the emission keeps serving the key it reads until then.QA
retired_flag_blocked_deploy_refuses_naming_the_replacement,the_retired_labels_are_no_longer_destinations,deploy_trigger_routes_to_flag_migration_even_when_green,worklist_row_red_prodpin_is_flag_migration,worklist_row_requires_redeploy_green_is_flag_migration,the_legacy_deploy_signal_is_read_from_the_body_not_the_title,a_deploy_confirmation_does_not_erase_the_repo_shape_signal,blocked_deploy_residue_is_producer_blocked_not_the_vetters_to_clear(the ai:ready-dominance leg),the_retired_blocked_deploy_count_keeps_its_key_and_counts_the_residue,flag_migration_leads_the_action_rank,the_producer_prompt_routes_deploy_shaped_blocks_to_the_migration,the_vetter_prompt_reads_a_red_prod_pin_as_migration_evidence_not_a_deploy_wait— each fails on base (on base the writer transitions instead of refusing,PRODUCER_STATE_LABELShas 3 members, the classifier answersdeploy, the signal type does not exist, and both prompts teach the deploy choreography; verified by construction against the pre-change code paths and by the mutation battery below, which reverts each behavior piecewise and watches the suite go red).modeled_state_label_short_circuits_to_parkedFAILED;!deploy_done_at_headsuppression dropped →deploy_trigger_routes_to_flag_migration_even_when_greenFAILED;legacy_deploy_signalstuck atNoneand, separately, blinded to red prod-pins → signal test FAILED both times; refusal stripped of theflag-blocked-onreplacement → refusal test FAILED; residue check deleted fromclassify_lane→ residue-lane test FAILED; emission'sblockedDeploykey re-pointed at the wrong bucket → kept-key test FAILED;as_strdrifted back to"deploy"→ worklist test FAILED;action_rankkeyed on the old spelling → rank test FAILED; prompt re-taught WAITING-DEPLOY / 3b(iv) reverted to DEPLOY → producer-prompt test FAILED both times; review-prompt reverted to "awaiting its deploy" → vetter-prompt test FAILED. 11/11 killed, tree clean after battery.blocked-infra, ai:reject and human:reject are one state: same owner, same action, split only by who ruled #133human:reject, ai:relink is a reject with a specific note — and the producer has no tool to execute it #135/retire the ai:relink verdict, and give the producer the linkage repair it never had #139ai:relink) and ai:blocked-on sits with the vetter: typed dependency refs, automated clearance on dep merge, human polling removed #164's clearance contract — expected behaviors derived from those records, not from the implementation under test.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation