Repository navigation
Model the merge queue the substrate already declares: merge_group admission, the merge_queue ruleset rule, and a sign-off gate on the live change - #10204
Conversation
…ission, the merge_queue ruleset rule, and a sign-off gate on the live change gunbc.repo_ruleset states that strict_required_status_checks_policy is false BECAUSE the merge queue is the construction that establishes the same invariant without re-running the floor for every PR behind a merge, and that the queue is not configured yet. The workflow already lists the merge_group trigger. Nothing declared the queue itself, so "turn it on" was a web-form change nobody in this repository could see -- the exact condition the ruleset module exists to end for the required context. Three things land, and no live setting is touched. 1. extdeps.github.rulesets gains the merge_queue rule: the MergeQueueRule tag, the two SCREAMING wire enums (grouping strategy, merge method) parsed into closed coproducts with unrecognized arms, and MergeQueueStanding with the same three states RequiredStatusCheckStanding has -- absent, configured, and more-than-one-refuses. Interface shape only; no policy. 2. gunbc.repo_ruleset declares the proposal with the SIGN-OFF IN THE TYPE. MergeQueueDesire is MergeQueueUnsigned | MergeQueueSignedOff, and only the signed arm yields a policy. Desired state here is ACTUATED -- converge PUTs the whole ruleset -- so writing the queue into desired_ruleset_rules would enable a merge queue on a live repository on one worker's edit. Under Unsigned the settings are declared, readable and diffed against the live ruleset, and contribute no rule to what converge sends. A live queue nobody signed off is reported with its grouping strategy, because an undeclared ALLGREEN queue and an undeclared HEADGREEN one are different guarantees. The same change closes a hole found while modelling it: the divergence fold only asked "is every declared rule present". The PUT is a whole-ruleset replace, so a rule that exists live and is absent from desired was DELETED by the next converge run with no divergence reported before or after -- the read-back compares against the same desired that dropped it. That is not a divergence to repair, because applying desired is what destroys it, so converge now REFUSES rather than writing (verify still reports it). 3. gunbc.merge_queue_admission models what a queue must satisfy: the exact-subject identity (the checked composition IS the commit that lands), the ordering chain (entry 0 onto the observed target tip, entry N onto entry N-1 -- which is what breaks when main moves under the queue), and the grouping strategy, which is where ALLGREEN and HEADGREEN answer differently. It carries the two terminals as separate constructors. MergeQueueOperational is "the queue can do its job"; MergeQueueExclusive is "no route around admission survives". Operational does not imply Exclusive -- a bypass standing leaves a route past every check while the queue runs correctly beside it -- and reporting the second on the first's evidence would be rung inflation on the axis a queue is adopted for. The type makes it unwritable. WITNESSES. Both files carry a positive control plus discriminating reds: an all-green chained queue admits, and the same queue admits nothing once only the observed tip differs; a green entry whose composition is not what lands is refused by identity; ALLGREEN and HEADGREEN are shown answering the SAME queue differently; the unsigned proposal is shown contributing no rule; and the actuation refusal fires on a merge_queue rule while not firing on the rule list this repository declares. LIVE READ-BACK, and what it did not establish. Read 2026-09-03 against gunb-ai/gunbc ruleset 16178731: rules are deletion, non_fast_forward, required_status_checks -- so merge_queue_standing is NoMergeQueueRule, merge_queue_divergences is empty under the unsigned desire, the actuation refusal does not fire, and merge_queue_terminal is Unconfigured. The repository now allows squash only (allow_merge_commit and allow_rebase_merge both false), which is why the proposal's merge method is SQUASH. bypass_actors is omitted from the response and current_user_can_bypass reads never FOR THIS READER, which is reader-relative by the module's own annotation -- so this read cannot establish Exclusive in either direction, which is precisely why Exclusive is a separate terminal rather than a field. The documented actuator route `gunbc run --entry .../repo_ruleset.dag --function verify` does not execute today: it refuses with ~3124 resolution diagnostics. Measured identical on origin/main in a detached worktree, so it is pre-existing and not introduced here; the live read above went through the same REST endpoints the model declares. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…eue timeout from the floor's own envelope The parse failure first: `grouping_strategy:` had its value on the next line in merge_queue_standing, which is not a field-value form the parser admits (rulesets.dag:331:29, "expected expression, found Newline"). Every "source annotation names no subject" line in that CI log is downstream of it. A local `gunbc compile` did not catch it -- the installed binary predates main's head -- so CI was the first honest reader. REVIEW 59188 (codex/gpt-5.6-sol), unit modelling, ACCEPTED AND FIXED. The reviewer is right that bare `Int` for minutes and for entry counts is a second authority beside std.measure. Applied along this module's own stated split: the WIRE stays bare, because GitHub sends plain JSON integers, and the DOMAIN does not. MergeQueueStanding and MergeQueuePolicy now carry `Minute` and a `MergeQueueEntryCount = Measure<Count, One, Nat>`. The distinction is load-bearing rather than cosmetic -- all six of those fields are bare integers on the wire, and only the carrier stops a wait in minutes being compared against a count of entries. THE TIMEOUT IS NOW DERIVED, WHICH IS THE POINT THE REVIEW EXPOSED WITHOUT NAMING. It was a transcribed 180 whose annotation claimed it came from the floor lane's job timeout. That is precisely the second authority the same review objects to, one level up: two numbers that must not drift apart, with no edge between them. `witness_floor_workflow` gains ONE row -- witness_floor_lane_timeout_minutes, value unchanged, the emitted YAML byte-identical -- and repo_ruleset reads it and adds a declared margin. The witness asserts the ORDERING, not the number, so raising the floor's timeout cannot silently invert it. A queue whose response timeout sits below the repository's execution envelope dequeues green work for being slow, which is a manufactured failure, not a real one. MergeQueuePolicy carries the COMPLETE documented parameter set, stated in the type's annotation: a `merge_queue_required: Bool` would say "queue on" while seven unmodeled parameters decided what the queue does, with provider defaults standing as the second authority. THREE CARRIER STATEMENTS, each because it was doing real work only in a message. Do not enable the queue in the GitHub UI, at the sign-off row where a future operator will be tempted: this module PUTs the whole ruleset, so a UI-enabled queue rule is unmodeled and the next converge run would delete it. A UI flip is not a shortcut past the model, it is a change with a scheduled silent self-reversal. The queue is not the shared-row remedy, as a stated non-goal: it composes trees, not two edits to one multi-kilobyte authority row. Receipt 2026-09-03, #10194 merged and knocked two sibling PRs DIRTY on one generated projection. The one-row/one-writer rule stands. The bounded gap, with its trigger, as a DissolutionCondition rather than a caveat: nothing here proves the required aggregate executes on a real merge_group composition, because no queue exists to compose one. The in-repo half is witnessed from the workflow authority; the world half dissolves on the signed flip plus one observed merge_group run whose subject commit equals the composition GitHub fast-forwards. And the bypass status is recorded as UNDETERMINED-BECAUSE-UNREADABLE, not as either answer: three reads on 2026-09-03 returned two results, the field is reader-relative, and no reader available here settles it. That is the shape MergeQueueExclusive exists to hold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
|
Addressed review 59188 (codex/gpt-5.6-sol) in Unit modelling — accepted and fixed. You are right that bare Your finding also exposed a second-authority one level up that it did not name.
Also in this push, from lane review: the sign-off row now states that a UI-enabled queue rule would be deleted by the next converge (whole-ruleset PUT), the queue's inability to compose two edits to one authority row is recorded as a stated non-goal, and the world-half evidence gap is a — sent from bold-ram-844 |
Review 59197 (claude/claude-opus-4-7) is right, and it caught the annotation contradicting the placement: the comment argued that std.measure already owns count-at-unit-scale and then minted a fresh alias for it in an extdeps module, which forks that axis once per upstream that happens to count something. MergeQueueEntryCount and its two accessors now live in std.measure beside HardwareThreadCount, CharacterCount, TokenCount and Millicore, and every consumer -- extdeps.github.rulesets, gunbc.repo_ruleset, both witness files -- imports them from there. No values change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
|
Fixed in
— sent from bold-ram-844 |
…cause the transport cannot answer a coproduct CI on ded1329 refused with seven `no field 'X' on type 'RulesetRuleParametersWire'` errors: `parameters` was statically typed to the required_status_checks shape, so merge_queue_standing could not read the fields GitHub sends for a merge_queue rule. The shape that says what GitHub actually sends is a coproduct discriminated on the rule's `type` tag. It cannot be READ. The REST realization decodes a nested object into an untagged map and this record is a static read-shape over it, so a coproduct arm would have no constructor tag to match on and every read would refuse. So the record becomes the UNION OF THE READABLE SHAPES -- what the transport can actually answer -- and the module states why rather than leaving it to look like a flat-record preference. That is safe only because of an invariant now written down: every read guards on the type tag first. required_status_check_standing and merge_queue_standing each `filter` on the rule type before touching a field, so no field is ever read off a rule whose parameters object does not carry it. The guard is load-bearing, so it is named as such, with a next-rung trigger stated as a capability -- a transport decode that carries a discriminator from the enclosing object into a tagged value, at which point the coproduct becomes readable and the guard becomes unnecessary instead of required. required_status_checks_rule keeps omitting the merge_queue fields, also stated: it builds the rule GitHub is SENT, and supplying zeroes for seven scheduling fields GitHub does not accept on a required_status_checks rule would be a fabricated value on the wire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
… rather than a note Operator ruling via the lane manager: the commissioning value is 1, and 5 is the intended change gated on receipts showing the one-entry protocol converging. The argument for 5 stands on its own -- building ahead of the head is what makes a candidate already validated when the head merges, and it does not weaken ALLGREEN -- and it is withheld for a reason that is not about throughput. With five compositions in flight, a failed commissioning run says which of five candidates was involved rather than which protocol step failed. A queue is adopted to make landings deterministic, so starting it at its throughput setting instead of its legible one inverts the thing being commissioned. Throughput is recoverable later; a muddled first receipt is not. Written as a DissolutionCondition rather than a TODO so it is retired by evidence -- entries composed, the required aggregate executing on each composition, successful candidates advancing -- and not by someone remembering. The row states that this is a legibility bound on commissioning, not a claim that 1 is the right steady-state value. A witness asserts the commissioned 1, so it cannot be raised without touching the assertion that names the trigger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…and record the always-bypass actor that settles the question
Separate Operational and Exclusive constructors did not close the defect,
because the EVIDENCE used to reach Exclusive answered the wrong quantifier.
current_user_can_bypass is a fact about the READER; "no route around admission
exists" is a fact about the RULESET's bypass_actors roster. One carrier let the
first masquerade as the second.
Two carriers now, named for their subjects. ReaderBypassStanding is
ThisReaderCannotBypass / ThisReaderCanBypassPullRequestsOnly /
ThisReaderCanBypassAlways / unrecognized -- the same four inhabitants review
56496 established must stay apart, renamed so the spelling stops naming the
ruleset. BypassActorRosterObservation is BypassRosterObserved { actors } |
BypassRosterUnavailable { reader, cause }.
AN ABSENT bypass_actors KEY IS UNAVAILABLE, NEVER EMPTY. GitHub omits it both
when the roster is empty and when the reading credential may not see it, and the
response carries nothing separating those. So the field is deliberately NOT
decoded onto RulesetWire -- adding it would decode absence to an empty list,
which is the exact conflation -- and every observation built from that endpoint
is Unavailable, carrying who asked and why. Only a complete, authorized
observation of an EMPTY roster may contribute to Exclusive, and there is no path
from Unavailable to `none` in first_bypass_route, so an unauthorized read cannot
mint the claim by construction rather than by care.
WHY A QUEUE IS NOT EXCLUSIVE IS ITSELF A COPRODUCT: AlwaysBypassActorObserved,
PullRequestBypassActorObserved, BypassRosterNotObserved. Three remedies -- revoke
the grant, revoke the narrower grant, obtain an authorized read -- and only the
third is about evidence rather than about the world. An unrecognized bypass_mode
reads as the WIDER grant, fail-closed.
AND THE QUESTION IS SETTLED IN THE POSITIVE DIRECTION. A privileged observation
reports an always-bypass actor on ruleset 16178731: actor_type RepositoryRole,
actor_id 2. The paragraph claiming no available reader could settle it is stale
and is replaced: Exclusive for this repository is not unproven, it is FALSE while
that grant stands. Four witnesses carry it -- observed-empty is Exclusive, an
unavailable roster cannot mint Exclusive and keeps its cause, the observed
RepositoryRole 2 always-grant holds the terminal at Operational, and the two
grants plus an unreadable mode do not collapse.
RepoRulesetConvergedEvidence.bypass_is_never becomes reader_cannot_bypass, for
the same reason: it was computed from the reader-relative field while its name
claimed the roster.
Merged origin/main 1ea6a94 first. The emitted witnesses.yml is byte-identical
to that main -- naming the subject this time, which is what I left out before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
… optional — and an incomplete rule refuses instead of defaulting CI on f7d0c06 refused with seven `missing required field ... in literal of type RulesetRuleParametersWire`. The behaviour I relied on -- that an omitted optional field is unwritten and only refuses at the read -- is not what this compiler does for a record literal: every declared field must be supplied. So `required_status_checks_rule` could not build its rule without inventing seven scheduling values GitHub does not accept on a required_status_checks rule. The merge_queue fields become optional and the constructor passes `none`, which is the true statement: this rule carries no merge-queue parameters. The required_status_checks fields stay mandatory, because a rule of that type always carries them -- the union now says something about presence rather than only about shape. That gains a wall rather than only fixing a build. merge_queue_standing REFUSES on a merge_queue rule whose parameters arrived incomplete -- MergeQueueParametersIncomplete { missing } -- instead of reading zeroes, so a truncated response cannot present as a configured queue. It surfaces as its own divergence naming the absent fields, and as Unconfigured at the terminal, because "a queue is configured" and "a queue is configured and we could not read what it does" are different facts. This is the second half of the type-tag guard: the tag says which fields to expect, the optionals say whether they came. Two witnesses: an incomplete rule names exactly the two absent fields and not the present one, and the rule this repository actually sends reads back as NO queue rather than an incomplete one. STATED, NOT ASSUMED: how the REST body serializer renders an absent optional in the PUT is not established by this change. If it emits explicit nulls, a converge run would send seven null keys on a required_status_checks rule and GitHub may refuse the body. No CI step exercises converge, so it has not been observed either way; the annotation says so rather than implying it works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
CI on e4d38a4 refused with one error -- `undefined variable 'id'` at rulesets.dag:275. The cause string spelled the endpoint as /repos/{owner}/{repo}/rulesets/{id}, and braces in a string are interpolation, so the compiler read {id} as a variable reference. The service block escapes its path templates for the same reason. Reworded to name the endpoint in prose rather than escaping the braces: this is a human-facing cause carried in a refusal, and the ESCAPED form would still read as a path template a caller might copy. The path itself is already declared once, in the operation's transport block, which is the authority for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…oundary rather than in the model Review 59242 (claude/claude-opus-4-7, non-blocking) is right and the diagnosis is exact: `witness_floor_lane_timeout_minutes` and `merge_queue_response_margin_minutes` were bare `Int` purely so that `+` type-checked, in a diff that is otherwise scrupulous about carriers. A duration kept as a scalar to make arithmetic convenient is std.measure's authority declined for the author's convenience. Both are `Minute` now, and the sum goes through `measure_add` rather than through raw integers. The unit disappears exactly once, at the Actions boundary: the job field is an Int, so the projection unwraps with `minute_count`. That is the right place for it -- the YAML key `timeout-minutes` carries the unit in its spelling, while the value handed to a second consumer in this repository carries it in its type. No emitted bytes change: minute_count(minute(count: 180)) is 180. The ordering witness compares two Minutes by their counts rather than a Minute against an Int, so it keeps asserting the relation and not a literal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
|
Fixed in the head commit. Review 59242's diagnosis is exact — both were bare Both are I took the first branch you offered rather than the marker: a — sent from bold-ram-844 |
The build lane refused: `regen FAIL generated surface drift: std_measure.rs`. Adding MergeQueueEntryCount and its two accessors to `dag/std/measure.dag` moves the emitted stage0 Rust with it, and that mirror is committed. Regenerated with the producer rather than hand-edited: claim_executor --required-regen over both source roots, and the drifted file taken from the candidate it wrote. Re-run over the updated tree reports `first_generation_equal=true`, which is the check the lane makes -- so this is the emitter's output, not a plausible transcription of it. The +13 lines are the MergeQueueEntryCount alias and its constructor and accessor, in the position the .dag declaration order puts them: beside TokenCount and its pair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…es stay inside the floor's CPU budget The floor refused with five INTERRUPTED-BEFORE-VERDICT rows, all at cpu_at_least ~501-507ms against the 500ms per-claim budget, and all five are the witnesses that reach merge_queue_terminal or floor_workflow_admits_merge_group. The cause is one field access. `witness_floor_workflow.on` forces the whole Workflow value -- every job, every step, every expression -- to answer a question about four trigger constructors. `witness_floor_triggers()` is the row the workflow's `on` block is assembled FROM, so reading it is not a shortcut past the authority: it is the same authority without the jobs attached, and deleting the merge_group trigger still drops the terminal. Worth naming as a shape rather than a tuning fix: a cheap question asked through an expensive carrier costs the carrier, not the question. Nothing about the witnesses changed -- five of them were paying for the CI workflow's entire job graph to ask whether a four-element list contains a constructor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…n now stops the line TWO THINGS, both closing gaps this PR had stated rather than resolved. THE SERIALIZER QUESTION IS ANSWERED BY EXECUTION. The PR said it was not established whether an absent optional in the shared parameters record is omitted from the PUT body or sent as an explicit null -- a wire fact a converge body depends on, and an actuation prerequisite for the sign-off. It now has a receipt: cli_run wire_body_omits_absent_optional_record_fields serializes a record carrying one present and one absent field through the same value_to_wire_json the REST body goes through, and requires the body to contain exactly the present one. The control is the same record with the field PRESENT, so a serializer that dropped everything cannot pass it. It runs in the required unit lane. So a required_status_checks rule sends its own three keys, not seven nulls. Reading the two mechanisms would have suggested the same answer -- the record arm skips nulls, `none` is the host null carrier -- and that is precisely why the test exists: a mechanism argument about a wire is not a wire receipt. SIGNING OFF WITHOUT WIRING THE PROJECTION NOW REFUSES. Review 59298 flagged, as a don't-forget, that desired_ruleset_rules appends no merge_queue rule under either arm. The consequence is worse than a forgotten follow-up: the day the arm flips to MergeQueueSignedOff and nothing else changes, converge would PUT a ruleset with no queue in it while verify reported a MergeQueueRuleAbsent divergence no apply could ever clear -- a signed policy silently not in force. Converge now stops the line and names what is missing, so the flip fails loudly at the moment it happens rather than being remembered. The check takes the policy as an OPERAND, which is what makes its RED authorable: the signed state does not exist in this tree, so a check reading the desire row alone would be permanently green by construction and would be cited as coverage while asserting nothing. The witness hands it the proposal as though signed and requires the refusal; the control passes an unsigned desire and requires none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…o row for it The crossing test came first: gunbc.recurring_failure_mode carries no cost-shape class today -- nothing about forcing extent, demanded grain, or a carrier priced above its consumer -- so this is a new row and not a second authority beside a standing one. The class: a claim needs one narrow fact and reaches it by naming a carrier that must be forced whole to yield it. floor_workflow_admits_merge_group read the FIELD witness_floor_workflow.on, forcing the entire Workflow value to answer a membership test over a four-element list, while the narrow producer sat beside it -- witness_floor_triggers(), which the workflow's own on: is bound to, so reading the function is not a copy of the authority but IS the authority. The harm is that the cost is charged to the QUESTION. Five claims crossed the 500ms per-claim CPU deadline and were reported as interrupted_before_verdict, which is not claims_failed: the PR goes red with a disposition naming no failing claim, and the author looks for a defect in logic that has none. The measurement is the row's content, and it is a shape rather than a magnitude: 501 -> 35, 501 -> 82, 502 -> 86, 507 -> 109, 505 -> 154, from run 33754056276's own required_floor_claim_cost.tsv at cost_basis=cpu. On the lightest, ~465ms of a 501ms claim was carrier and ~93% of the cost answered nothing the claim asked -- the CHEAPEST question paid the most, because forcing cost is denominated in the carrier while the question's own work is what varies. Rung mitigatable, stated honestly: nothing refuses a field read whose forcing cost exceeds its consumer's demand, and the cost ledger is a detector that fires only after the claim is authored, enrolled and interrupted -- which is why these five were found by a red and not by review. The next-rung trigger names the capability and not an artifact: a demanded-extent producer, sufficient to refuse or narrow a read that forces strictly more than its consumer observes. The cost line is explicitly not that trigger; a line separates expensive from cheap and says nothing about whether the expense was demanded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
# Conflicts: # dag/gunbc/recurring_failure_mode.dag
# Conflicts: # dag/gunbc/recurring_failure_mode.dag
…a value lifted out of it The first form of this check asked whether the desired rule-TYPE roster contained a merge_queue entry. Two defects, one inside the other. The roster was a second hand-maintained list beside desired_ruleset_rules -- one fact with two authorities, agreeing by diligence. A rule added to the projection without a matching hand-edit was invisible to every reader of the roster. It is now derived: ruleset_rule_types(rules: desired_ruleset_rules()). The deeper one is that a rule TYPE says nothing about the seven parameters that decide what the queue does. A merge_queue rule projected with the wrong strategy carries the right type and the wrong queue, and the check passed on exactly that input. The second form compared against merge_queue_standing(rules) -- the reader that interprets a live GET. That is not a JSON round trip, but it is still a value lifted out of the subject: it compares the policy to the result of a parse rather than to what the PUT carries, which is the roster defect one layer down. This form walks the projected RulesetRuleWire list itself and compares each declared setting, rendered to the spelling GitHub is sent, against the exact optional the serializer receives. The policy side goes through the wire labels because a declared AllGreenGrouping and a projected "ALLGREEN" are only comparable in one vocabulary, and the upstream's own spelling is the one that lands in the body. One value, two consumers, neither reading the other's output as evidence: the admission check asks whether this policy is correct, of the typed list; wire_body_omits_absent_optional_record_fields asks whether that typed list reaches the wire intact. The structural arms mirror merge_queue_standing deliberately -- absent, ambiguous, incomplete, then the field walk -- because the two must agree about what a well-formed projection is even though they answer different questions, and where they would disagree the disagreement is the finding. WHY THE PROJECTION STILL CARRIES NO QUEUE RULE, now stated as a structural blocker rather than a to-do, because the difference decides whether this wall is a guard or a placeholder. RulesetRuleParametersWire is one flat union, and two of its fields are non-optional Bools belonging to required_status_checks. A merge_queue rule built through it cannot omit them, and sending them would put keys on the wire GitHub does not accept there -- the fabricated wire value the required_status_checks constructor refuses in the other direction. The capability that ends it, named as a capability: a wire decoding that admits a coproduct discriminated on a sibling key. Until then every arm of the wall is exercised against a fixture rule, which DESIGN 4b explicitly sanctions -- a state unrepresentable in the accepted corpus may still be authorable as source handed to the compiler, and declining to enroll it there is specification-without-execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…, not the monolith The operator has frozen dag/gunbc/recurring_failure_mode.dag until the per-class split lands, and the reason to back this out is stronger than the freeze. After the split a new class needs two edits -- a per-class row file plus a roster import and entry -- so a row landed in the monolith today is an edit that must be undone and re-authored in a different shape within the day. That is the throwaway artifact DESIGN section 6 says to back out of rather than schedule around. The row is genuinely severable from the rest of this PR, which is why the split is honest rather than cosmetic. It documents a cost-shape class found while measuring this lane's own floor red; the merge-queue model neither produces it nor consumes it, and nothing in the remaining diff resolves, imports or cites it. The measurement that is its content -- 501 to 35, 501 to 82, 502 to 86, 507 to 109, 505 to 154, cpu-vs-cpu against the interrupting line -- is already carried in the PR body and in the annotation on floor_workflow_admits_merge_group, where it explains a construction rather than filing a class. Held for re-landing after the split, authored in the split shape from the start, so it never touches the monolith at all. This also removes this branch's only claim on the file every other lane is appending to, which is where both of my merge conflicts today came from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…authority derives The generated-artifact merge driver refused this path rather than answering, which is it working: both sides changed docs/design-failure-modes.md since the merge base, and taking a side blind is what it exists to stop. Resolved by asking which authority each side was derived FROM, not which side is newer. This branch's dag/gunbc/recurring_failure_mode.dag is now byte-identical to main's -- the cost-shape row was withdrawn for the split, so the branch holds no claim on that file at all. The projection that derives from that authority is therefore main's, which is the side this merge already carries. The heal commit's bytes were generated before the row was withdrawn, so they are a projection of an authority state that no longer exists anywhere. Verified by identity rather than by count: the withdrawn row appears nowhere in the resolved projection, and authority and projection now come from the same 81 rows because the authority file is main's file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…: eight refusals, eight repairs The floor did not judge a claim and find it wrong. It REFUSED during strict preparation -- phases_run=3 phases_failed=1, no required-floor summary line at all -- so nothing here executed. SEVEN of the eight are #10226's new wall catching pre-existing mismatches that had no obligation producer until this evening. Three test helpers were declared -> List<String> while observed_ruleset_divergences returns List<RulesetDivergence>, and every call passing one to render_divergences was a generic-argument mismatch nothing had ever judged. The declared type was simply wrong and had been wrong since before this branch; the wall's first outing in the wild found it. Fixed at the declaration rather than at the seven call sites, because the call sites were correct. THE EIGHTH IS MINE AND IT IS NOT THE KNOWN if-JOIN FALSE POSITIVE, which I checked rather than assumed because the shape is identical to the defect filed in #10232. That defect makes a WELL-TYPED construction fail: the join compares branches against each other and never against the declared return context. Here the construction is not well-typed under either rule. concat yields String, the declared payload is NonEmptyStr, and String does not inhabit NonEmptyStr -- so a join that DID consult the declared return would refuse this too, for the same reason. The repair is the cast the corpus already uses for a concat whose first operand is a non-empty literal, and it is safe by construction rather than by check. Both refusal messages take it, not just the one the floor reached. The second was on the same path and would have refused on the next run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
# Conflicts: # src/v1/stage0/src/std_measure.rs
…solving its conflict Merging main conflicted on src/v1/stage0/src/std_measure.rs, which is a GENERATED artifact: both sides had added rows to it because both sides had added rows to dag/std/measure.dag. Resolving those bytes by hand would have been an author writing a projection, and either side taken whole drops the other's rows silently. So the conflict was not resolved, it was DISCHARGED by the producer. The authority merged cleanly on its own; the mirror was regenerated from it with claim_executor --required-regen and the candidate installed verbatim. Taking main's side first was only to get a tree that compiles well enough to build the regenerator -- a deliberately stale intermediate, never a resolution. The receipt is the producer's own predicate rather than a diff I read: first_generation_equal=false before, first_generation_equal=true after, planned=156 executed=156 adjudicated=156 on both runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…at it must be sufficient for Review 59455 is right that the trigger did not qualify as written. It named a capability with no owner and no bound, and 4b(3) is explicit that a trigger naming only an artifact is satisfied while the capability stays dead -- the symmetric failure is a trigger naming only a capability, which anything can claim to satisfy. The producer is v1_interpreter json_to_value. Its object arm collects every key into a map_value with no type context and no discriminator, so a nested JSON object reaches the substrate as an untagged map and a coproduct arm has nothing to select on. That is the whole mechanism, and it is one function. What it must be SUFFICIENT FOR is now stated rather than implied: a decode of a rule object must be able to yield a value whose parameters is the merge_queue ARM, not a map that a later guard classifies, so that reading a required_status_checks field off a merge_queue rule becomes a resolve refusal instead of a read this module prevents by filtering first. Anything less leaves the guard load-bearing and this class stays at mitigatable regardless of what lands. And the obvious objection now has its mechanical answer in the carrier rather than in a review thread: declaring the coproduct today changes nothing about what can be READ, because the decode produces the same untagged map either way. Every arm would refuse, and merge_queue_standing would report NoMergeQueueRule for a ruleset that has a merge queue -- a silently wrong answer replacing a correct one, which is the direction DESIGN section 5 forbids outright. The declaration is not deferred for convenience; it is unreadable until its producer changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
|
Both findings in review 59455 verified against the current code. One is fixed in Finding 2 — the undeclared sum: trigger strengthened, declaration still refusedYou are right that the trigger did not qualify as written. It named a capability with no owner and no bound. §4b(3) names the symmetric failure — a trigger naming only an artifact is satisfied while the capability stays dead — and a trigger naming only a capability is one anything can claim to have satisfied. Fixed:
On "declare the parameter coproduct now" — I am not doing that, and the reason is mechanical. Declaring it changes nothing about what can be read, because Finding 1 — the constructor predicates: declining, with the check I ranThe prescribed remedy is to consume "the canonical fold/query surface". I checked whether one exists before declining, and it does not. There is no fold or query surface over Two further points, offered as reasoning and not as prior art:
I acknowledge both findings are ctrl review policy rather than — sent from bold-ram-844 |
Review 59488 flagged three constructor predicates. Checking them found a real defect its stated rationale does not name, and it is mine: I wrote workflow_trigger_is_merge_group out exhaustively when the earlier review asked for it, and left rule_is_merge_queue on `_ => false` three hundred lines away in the module I was already editing. Two predicates over two coproducts, the same question, two different answers about what an unknown constructor means. A wildcard here answers for constructors that DO NOT EXIST YET. GitHub adds rule types -- this module has already had to carry UnrecognizedRuleType for the ones it does not model -- so a new arm on RulesetRuleType would be silently classified as not-a-merge-queue rather than failing here until someone decides what it means. That is the fabricated default DESIGN section 5 reserves for a refusal. rule_is_required_status_checks carried the identical wildcard and is older than this work. It is repaired with its sibling, because leaving one of two identical predicates exhaustive makes the pair a worse guide than either choice applied consistently: whoever adds a third copies whichever one they happened to read first. It is not repaired because it was flagged -- it was not flagged -- but because the flag was right about the shape, and this is the same shape in the same module guarding the same coproduct. No wildcard arm now remains in any of the three modules under review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
|
Review 59488's three sites verified. One was a real defect and is fixed in What was actually wrong, and it was mine
That wildcard answers for constructors that do not exist yet. GitHub adds rule types — this module already carries
Declining the fold-surface remedy, with the checkThe finding offers two remedies. On the first — consume "the canonical fold/query surface" — I checked whether one exists before declining, and it does not. There is no fold or query surface over On the second — "the required bounded disposition with owner, lane, and trigger" — I am not filing one, because I would be inventing debt that does not exist. All three predicates now match exhaustively and each lives in the module that declares its coproduct:
I acknowledge this is ctrl review policy rather than a — sent from bold-ram-844 |
…r half, left open desired_ruleset_rules projects no bypass_actors and converge sends the WHOLE ruleset, so an apply against a live ruleset carrying an actor grant DELETES it, and nothing in the divergence fold reports it -- the fold compares rules and required contexts, and a bypass actor is neither. This repository has an observed always-bypass RepositoryRole actor id 2, so that is a live silent destructive write and not a hypothetical. It is the same class rule_unexpected_divergences already closes one field over. The two halves were written days apart and only one was closed; the asymmetry is the defect, not the actor. It refuses rather than repairing for the same reason: "the world is not what we declared, apply desired" is exactly backwards when applying desired is what destroys the thing. THE UNREADABLE ARM IS THE ONE THAT MATTERS MOST. BypassRosterUnavailable means GitHub omitted the key -- indistinguishably because the roster is empty or because this credential may not see it -- so an apply would delete either nothing or an unknown number of grants and the observation cannot say which. Treating unavailable as empty would let the empty-observation narrow decide a destructive write, which is the exact inversion this module's reader-versus- roster split exists to prevent. It refuses instead. Three witnesses, including the positive control on an OBSERVED empty roster, without which the check could be "always refuse" and both REDs would still pass. This closes a hazard; it does not discharge the standing hold on modelling the bypass carriers, and it is a reason that hold is right rather than a reason to hurry the sign-off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…pair Closing the write half left the reporting half open, and the asymmetry was the same shape as the one it repaired. observation_divergences bound the roster to `_`, so an undeclared bypass actor was invisible to verify even after converge learned to refuse on it. The refusal stops the write and tells nobody the grant is there -- which is the wrong half to have alone, because the operator's question is "what is on this ruleset that we did not declare", and verify is what answers it. The rule side already had both halves: RulesetRuleUnexpected reported by verify, actuation refused by converge. This is that pair completed for actors, with the same two constructors and the same argument. AN UNOBSERVED ROSTER IS ITS OWN DIVERGENCE RATHER THAN SILENCE. Reporting nothing for BypassRosterUnavailable would collapse "we could not see whether a bypass exists" into "no bypass exists" -- the exact distinction the reader-versus-roster split was built to keep -- at the moment a reader is asking. So it renders as UNKNOWN rather than none, naming the reader. Three witnesses including the positive control on an observed empty roster, so verify cannot invent a finding on a ruleset that genuinely grants none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL
…he read-only trigger FLOOR BLOCKER, from required run 33908433503: `2 consumed admission(s) due for deletion on this roster-touching change`. Both `kernel-identity predicate relocation gunbc#10350` rows were consumed by their own merge into this branch's base. Their entry named this roster's next touch as when the deletion comes due; this change is that touch. Deleted, with KERNEL_IDENTITY_RELOCATION_LABEL. Adjudicated by the declaring-module join rather than the trigger sentence, on that entry's own rule: `resolved_node_is_kernel_identity_for_name` is declared exactly once in main's tree, at module v1.std.core, and both named callers still reference it -- so base and head bind the same declaring module and the delta is unproducible. The join was calibrated: a fabricated name answers NOT DECLARED against that module while `kernel_span` answers DECLARED, so both verdicts were reachable. Deletion pass asserted examined == kept + deleted (4 = 2 + 2) against a brace-depth parse, not a grep. The claim in the TWENTY-FOURTH TRANSITION that the merged array is four rows is corrected in place rather than left to rot, since it is now false. ALSO, from review 60315's non-blocking remark. It observed that WorldApplied is never constructed. The type stays: it is a variant of the subject-agnostic WorldActuation shape, and that no currently-bound subject constructs it is a fact about repo_ruleset's binding, not about the shape -- deleting it would leave the generic fold unable to express a successful actuation at all. But the remark points at a real §4b(3) defect it did not name: the read-only standing named #10204, an ARTIFACT, as its trigger. Both sites now name the CAPABILITY and what that change must be SUFFICIENT FOR -- a write preserving the live bypass-actor roster AND a post-apply read-back observing it survived -- and say explicitly that #10204 merging is not itself the trigger. This is the grain whose absence this same module already records paying for once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4
…up's Optional, and delete the bare ruleset PUT (#10324) * Generalize fleet converge into world convergence * Name the ruleset actuation hold honestly * Declare the world-convergence witness fixture types so the witness executes FixtureDifference and FixtureReceipt were single-name `type X = Y` forms, which this grammar reads as ALIASES, so both aliased a type that was never declared: the module never resolved and the hub's only claim had never run. Its green was the absence of a run, not a passing one. Declares FixtureDrift and FixtureApplied as records used directly at each position -- the aliases are gone rather than repaired, since a second name for one fixture type is the nickname §3 forbids. Adds the positive control the ladder requires beside the refusal claim: without it, a root that refuses every subject satisfies the RED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Delete apply_desired_ruleset: the bare whole-ruleset PUT is gone, not guarded World convergence took the actuation root, leaving the unguarded github.Rulesets.Update with zero callers. Zero callers make the deletion the census: nothing refuses, so nothing was load-bearing. Keeping it would leave a live destructive write reachable by the next author who greps for "how do we apply a ruleset" and never passes repo_ruleset_actuation_admission -- the §3 attractor. Actuation returns through the admission or not at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Restore the Optional at the host lookup, and declare the difference type the handler names Two defects at the fleet_converge_apply match site, both reported far from their cause. The generic find_by_identity returns T?, and the Optional does not survive inference: matched directly, its result reads as bare HostConverge, so Present is reported as a missing variant of HostConverge at that type's declaration -- an innocent line in another file. Two callers already worked around it with a private monomorphic wrapper each; this promotes ONE wrapper beside the type, names the compiler deficit and its dissolution trigger there, and routes all three call sites through it. `type HostConvergenceDifference = HostConvergenceRequired` is an alias, not a one-variant coproduct, so the handler's difference parameter named a type that was never declared -- the same defect as the hub witness fixtures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Lift the two body-position annotations to their declaration §4c admits only standalone leading // blocks attached to module-scope declarations; a comment inside a match arm is not capturable and refuses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Adjudicate the namespace wave the Optional repair moves, and pay the roster's consumed-row debt The required floor refused with three unadjudicated namespace deltas, and the first of them was a real defect, not paperwork: fleet_converge_apply still CALLS find_by_identity in fleet_compute_host_for_identity, and the import-list edit dropped it, so that binding went from {gunbc.host_converge} to {} — NewUnresolvedness. The witnesses stayed green over it because the bare name still resolved through the pool; the wall is what saw it. The import is restored. The other two are the move itself: host_converge_for_identity's two callers in fleet_converge_cli now resolve to gunbc.host_converge. That is TargetChanged and is not auto-admitted, so it gets two exact admission rows naming module, declaration, spelling and target — enumerated by identity, never a wildcard over "anything that moved". Touching the roster makes its consumed rows due. The required run reported six — two from gunbc#10206 and four from gunbc#10028 — as satisfied at this base, and this module's own convention charges their deletion to its next toucher. They are deleted here rather than left for a later change to trip over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Split the held-actuation cause: an unsigned desire is not an unmodeled live rule Review finding from witty-otter-195 on #10324, and it is correct. merge_queue_projection_refusal returns one Present for two different facts — a live rule this projection cannot express, and a signed-off merge queue the desired projection does not carry — and the admission mapped every Present to UnmodeledLiveRuleActuationHeld. Only the first path is reachable today because desire is unsigned, so the collapse would become a wrong diagnostic on the first sign-off, which is exactly when nobody is re-reading this arm. It also left RepositoryRulesetUnsignedDesireActuationHeld as a declared variant that nothing on the observed path constructs. The admission now reads the two halves separately and names the cause that actually holds. The combined helper keeps its located refusal prose and its two enrolled claims, with its standing — no production caller — recorded on the declaration rather than left for a reader to mistake for a live gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Delete required_context_reconcile: the membership plan this module no longer computes The reconcile step's only consumer was the whole-ruleset actuate root world convergence replaced, so it was left with exactly ONE occurrence in the tree — its own declaration. No caller, no test, no import. It is an orphan this PR created, not one it inherited, and delete-first says the deletion is the census. It is disposed of by deletion rather than kept with a standing note, and the distinction from merge_queue_projection_refusal is the reason: that helper is retained because deleting it would destroy authored located refusal prose and two claims that execute over it. This one has neither. Nothing is lost by the cut, and silence plus one declaration is how a reader concludes a reconcile step runs. The cut is the census: it takes its four private helpers, its member-at type, the gunbc.membership_reconcile and gunbc.ownership imports that only it used, and the carrier note describing the Owned-versus-Ensured choice it no longer makes. desired_required_status_checks survives — the desired projection still reads it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Re-enroll the transport RED the cut retired, and stop the admitted arm asserting the negation of its own precondition Two review findings on #10324, both verified against the tree before acting. THE DROPPED WITNESS IS REAL. witness_emit_artifact_transport_rejects_shell_command exists on main and not on this branch; its replacement covers unknown identity, a different class, so the bash-emit-versus-local-shell refusal lost its enrolled RED. It could not survive verbatim — it called converge_apply, which world convergence replaced — but §4b(4) retires the obsolete PRODUCTION machinery and never the class's evidence. It is re-enrolled against the new root with the same subject: an EmitArtifactThenThinRun transport must not silently converge. Identity, policy and handler match the sibling claims, so the transport is the only varied input and this green cannot be borrowed from another arm. THE ADMITTED ARM ASSERTED THE NEGATION OF WHAT ADMISSION ESTABLISHED. Admission is reached only when the bypass roster projects, no live rule is unexpected, and the desire IS signed — so refusing an admitted subject with UnsignedDesireActuationHeld says the opposite of the fact just decided. The arm is unreachable today, so it misdiagnoses nothing in production; a diagnostic that only becomes wrong once a writer lands is wrong at the one moment anyone reads it. The cause is now ActuationUnbound, which is the state that actually holds: no admitted write realization exists on this tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * The held-actuation causes were INVERTED: say what is true of the values that construct them Review 5109503332, verified against the tree. Two production causes asserted the negation of what their own inputs establish, and one of them is the live path. IGNORANCE PROMOTED TO AN OBSERVATION. bypass_roster_projection_refusal answers Present for an UNREADABLE roster and for an OBSERVED non-empty one alike, and the admission folded both into one held cause rendering "live bypass actor cannot be projected". Under the credential this repository has, a live ruleset GET omits bypass_actors on every read — GitHub omits that field both when the roster is empty and when the reader may not see it — so on the path we actually execute, converge reported THAT A LIVE ACTOR EXISTS when the truth is that this reader CANNOT SEE whether any do. That is top-as-answer standing in for top-as-ignorance, asserted rather than merely widened. SIGNED-BUT-UNPROJECTABLE REPORTED AS UNSIGNED. signed_desire_projection_refusal is Present only when a policy IS signed and the projected rule drifts from it, and that was mapped to a cause rendering "desired ruleset is not signed" — which sends an operator to sign something already signed. The admission now reads the observed values directly and preserves five distinct standings, each carrying enough payload to render the actual fact: BypassRosterUnobservable (with the reader, because the refusal is a property of the credential and not the moment), ObservedLiveBypassActors (with the actors), UnexpectedLiveRule (with the divergences), SignedPolicyProjectionUnrepresentable (saying signing again fixes nothing), and the terminal ActuationRealizationUnbound. verify's exit arms render each. THE NEW CLAIMS CROSS THE REPLACEMENT JOIN, which the retained ones do not: they call repo_ruleset_actuation_admission itself, not the helpers whose text no production path emits any more. The unobservable-versus-observed pair is the discriminating RED for the inversion — the same fixture with only the roster varied — and the empty-roster terminal cause is the positive control without which an admission that refuses everything identically would satisfy them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * The standing note's own trigger had fired and retired nothing: name the third outcome The note on merge_queue_projection_refusal read "dissolve-on: the admission constructing its own located causes, at which point this prose has a consumer again or has nothing left to say". The admission now constructs them, and NEITHER arm happened: the helper did not get a consumer back — the admission deliberately does not route through it, since folding two facts into one Present is what inverted the causes — and it does not have nothing left to say, because it answers the coarser may-converge-actuate-at-all question in prose no cause carries, that applying desired would DELETE what the projection omits. The fired trigger is recorded rather than quietly swapped, because a trigger nobody re-reads after the event that fires it is the failure this repository keeps paying for: usually as a trigger naming less than the capability it governs, satisfied while that capability stays dead, and here as its mirror. The replacement names a capability and not an event: an executing claim asserting the DELETE-hazard prose through the admission's own located causes. Nothing smaller retires it — in particular the admission merely HAVING located causes does not, which is the mistake the fired trigger made. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * A variant that erased its own distinction: delete WorldAbsent rather than explain it WorldAbsent carried the same Observed payload as WorldObserved and nothing else, and its arm in world_converge was byte-identical to WorldObserved's. The carrier note said the collapse was deliberate because absence "proceeds through the same subject-owned difference operation" -- but the difference operation receives only the payload, so it could not tell the two apart. The distinction was not delegated to the subject, it was DESTROYED before the subject could see it, while a note stood there reading as coverage. Latent, not live: zero producers corpus-wide, so no path reached the arm. But world_converge.dag does not exist on origin/main -- this PR INTRODUCES the file and the variant, so merging is the clock and the residual is this PR's to close, not a follow-up's. Of the two shapes, deletion over a discriminating payload: a discriminator cannot be got wrong if the variant does not exist, and re-adding it when a real producer appears is a smaller diff than a note explaining why the payload is identical. This is a climb to structurally impossible -- the misleading state now has no constructor -- so it dissolves the note rather than repairing it. The fold's existing discriminating RED and positive control stay enrolled unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Two of five causes had no executing discriminator, and the unreachable one was the inverted one The replacement-join battery varied the bypass roster, which discriminates the roster arms and leaves the other two untouched: the fixture pins rule_types to the declared list, and the signed-policy arm is decided by signed_desire_projection_refusal, which is NULLARY and folds two module-scope globals. While this repository's merge queue is unsigned that arm is unreachable through ANY observation, so no fixture could reach it. Counting claims that cross the join answered a cardinality question; this answers the coverage one. AN UNREACHABLE ARM IS EXACTLY THE ARM THAT NEEDS ONE. This branch was WRONG while it was unreachable -- it reported a signed-but-unprojectable policy as "not signed", sending an operator to sign what was already signed -- and unreachability is what let the inversion sit. "The source reads correctly today" is not a wall against the same inversion returning at the first sign-off, which is the moment the arm goes live and the moment it is first read. repo_ruleset_observed_actuation_admission is the ordered cause mapping, split out so every arm is reachable by an executing claim. It is NOT a fixture double: repo_ruleset_actuation_admission holds no copy, decides only readable-versus-unreadable, and delegates every observed case here, so production and the claims execute one function. Only the signed refusal arrives as a parameter -- production passes signed_desire_projection_refusal() and no fact about which policy is signed leaves the module. The two new claims prove the MAPPING, not the predicate: the fixture supplies the Present, so they do not establish that production constructs it correctly. That is the right cut rather than a gap, because the defect WAS in the mapping, but it is stated here and in the PR body so "the signed-policy arm is covered" is not later read as covering the predicate too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * The transport witness did not discriminate transport, and its own note said it did The re-enrolled claim accepted any refusal constructor through a wildcard, while its note read "the transport is the only varied input and a green here cannot be borrowed from another arm". That sentence was false when it was written: the sibling knob-frontier claim uses this same fixture under LocalShell and already refuses, so substituting LocalShell left the witness green. A claim named for a boundary, passing for a reason unrelated to that boundary, is worse than no claim -- it is cited as coverage for a wall it never touched. That is the same shape as the WorldAbsent note deleted earlier in this branch: prose asserting a discrimination the code does not make. DESIGN 4c says an annotation is never evidence that a machine claim holds, and the structural version of that rule is to put the control INSIDE the claim rather than beside it. Both halves are now asserted together: the emit transport must refuse with the reason naming the fresh-standup bootstrap boundary, AND the identical fixture under LocalShell must NOT satisfy that same assertion. A realization that ignored transport and answered both alike fails the second half, so the claim cannot be satisfied without the distinction existing. Routing EmitArtifactThenThinRun through the in-process arm turns the first half red and leaves the LocalShell half untouched. operator_host_srv3 maps to FreshStandup, so the transport-specific arm genuinely fires rather than falling through to realize_converge_in_process. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * verify's note named a variant that does not exist The header said drift "reaches RepositoryRulesetActuationHeld". That variant has no declaration anywhere in the corpus -- the name was introduced by this branch's own cause rewrite and never corresponded to an arm of RepositoryRulesetConvergenceRefusal. This is the third instance in this PR of the same defect: prose asserting something the code does not carry. The other two were a note claiming a discrimination the signature could not provide and a note claiming a variation the assertion did not make; this one names a symbol that was never declared at all. DESIGN 3 makes exactly this decidable -- a name is reachable from the containment tree, so a stale one is greppable and enforceable where a stale line number is not -- and it went unnoticed because nothing joins a comment to the type it describes. Named to the real shape rather than to a single arm, because drift does not reach one cause: it reaches whichever of the five located causes holds, and only terminates at ActuationRealizationUnbound when nothing more specific does. Substituting the nearest live variant would have restated the same over-narrow claim with a name that happens to compile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * The apply note argued about a cause inversion in the vocabulary of the inversion world_repo_ruleset_apply's annotation named two variants with zero declarations anywhere in the tree. Both were deleted by this branch's own cause rewrite, so the note describing the fix was written in the vocabulary of the world it replaced. UnsignedDesire no successor -- no live cause carries an unsigned standing ActuationUnbound RepositoryRulesetActuationRealizationUnbound THE TWO ARE NOT THE SAME EDIT. ActuationUnbound is a clean rename: the sentence stays true with the live name in it. UnsignedDesire has NO successor, so renaming it is unavailable, and substituting the nearest live cause would be worse than leaving it -- SignedPolicyProjectionUnrepresentable means "signed but unrepresentable", which is not the negation admission establishes, so the illustration would be false in a way that parses. The sentence is reworded to state the principle over the admission's actual preconditions, and says what is now true instead: no such cause remains CONSTRUCTIBLE, because the inversion was fixed in the type rather than in the arm. That is a stronger claim than the original made. Also expands ActuationRealizationUnbound at the admission's own note to the declared name. DESIGN 3 says cite the symbol; an abbreviation is not the symbol, and prose dropping a common prefix is exactly what a substring matcher cannot distinguish from a live reference. Swept the whole file rather than the two names reported: a token matcher over every comment against every non-comment line tree-wide now returns no unresolvable name in this file. Two dead names remain in other files this PR touches -- ConvergeSparkPreEnrollment in fleet_converge_cli and PerSlotUnitProperty in host_converge -- and both are left alone deliberately: they pre-date this branch on origin/main and this diff does not touch their lines, so they are a pre-existing corpus defect and not this PR's residual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Pay the roster-touching obligation: delete the 19 consumed fleet_asset_identity admissions Required run 33854290231 refused this branch on namespace-wave-admission -- 0 unadjudicated deltas, 0 stale admissions, and consumed rows due for deletion on a roster-touching change. This branch touched the roster to admit its own two rows and thereby inherited the deletion of nineteen it did not cause, which is the rule the TWENTIETH DISSOLUTION recorded as recurring. Fifth firing: 57, 3, 47, now 19. THE FLOOR RED WAS NEVER COST. The failing step is D0-ADJUDICATE, which consumes the measurement receipt rather than running claims, its standing is measurement_completed, and the job reports completed_over_cost_requirement=0. The aggregate said as much on its own -- mechanism=unestablished attribution=unestablished, with an explicit instruction to read the log before assuming a defect in the diff. An "unestablished" is a refusal to attribute, and it was read as an attribution already held. DECIDED PER ROW AGAINST THE CURRENT BASE, not from the reported count. All ten distinct spellings -- six chassis, two coolers, pdu_01, switch_01 -- are declared in fleet_asset_identity.dag on main and imported from there by fleet_physical_inventory, so all 19 deltas are unproducible. The reported count was 16 against a head predating this branch's merge of main; the merge then took main's roster wholesale and carried #10344's rows in. The consumed set is a property of the base one sits on, not the base one measured under. THE TWO SURVIVING ROWS ARE THE POSITIVE CONTROL, without which this is a sweep rather than a paid trigger: host_converge_for_identity is NOT in gunbc.host_converge on main and IS still in gunbc.fleet_converge_cli, so its delta is still producible and its rows stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * A dissolution trigger at the wrong grain retires the row while the deficit still bites `host_converge_for_identity`'s trigger read "Dissolves when generic Optional returns infer" -- five words that name a capability but never say what it must be SUFFICIENT FOR. DESIGN 4b(3) makes that the review tell: a corpus-wide loss under a trigger stated at no particular grain is satisfiable by something far smaller than the capability it claims to restore, so the row would retire while three call sites still need the wrapper. 4b(2) calls the same thing an untracked stall. This restates it at the grain of the loss: inference carrying the Optional through a generic return position, sufficient for every consumer to match on such a call with no monomorphic wrapper in front of it -- and names what does NOT retire it, since the failure mode is a near-miss satisfying the sentence while the deficit stands. Prose only; no behaviour, no declaration, and no change to the annotation's shape -- it was a leading block attached to a module-scope declaration before and after. The wording follows the trigger already carried by `merge_queue_projection_refusal` in `gunbc.repo_ruleset`, which was written to this grain in this same change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Delete the host world-convergence binding: its differences() asserted what its type says it computes Review 60232, upheld. `host_convergence_differences` ignored all three parameters and returned `WorldDiverged` unconditionally, so `WorldInAgreement` was unreachable for hosts, an already-converged host was unrepresentable, actuation was always entered, and `unchanged_host_convergence_receipt` was dead by construction. §5's fabricated plausible output, at the function whose whole job is deciding. NO BETTER HANDLER WAS AVAILABLE, which is why this is a deletion and not a repair. `WorldDifference` is exactly `WorldInAgreement | WorldDiverged` with no cannot-decide arm, and the host realization FUSES OBSERVATION INTO ACTUATION -- convergence state is discovered by converge_apply_for_host in the act of applying. There is no honest differences() to write for this subject, so the handler had to lie in one direction or the other. Trigger, as a capability: a host observation that reads convergence state WITHOUT actuating. Recorded in the PR body, not in docs/design-failure-modes.md, which is under an active landing lease. THE WITNESSES ARE RESTORED, NOT DELETED. Five of the six claims in fleet_converge_apply_witness_test.dag PRE-EXIST this change; this branch had migrated them onto the new binding, so reverting the migration restores their original subject rather than dropping coverage -- §4b(4) retires obsolete production machinery, never the class's evidence. Only the sixth, which existed solely to exercise the deleted binding, goes with it. fleet_converge_apply.dag is byte-identical to its base, which also restores converge_apply / FleetConvergeApplyResult: this branch had deleted the old path in favour of the binding, and with the binding gone the frozen path is what the witnesses call. Zero dangling references corpus-wide to host_world_convergence_handler, HostConvergenceSubject, HostConvergenceRefusal or observe_host_convergence. WHAT IS UNAFFECTED: the repo_ruleset binding, which is honest and is the sole production consumer of world_converge -- observe_repo_ruleset reads live state and observation_divergences computes a real divergence, so its InAgreement arm is reachable. The host_converge_for_identity promotion also stands on its own: three consumers remain, and the Optional-through-inference defect is a property of the lookup rather than of the binding that was removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Pay the consumed-row obligation: gunbc#10355's 30 rows are satisfied at the base The namespace-wave-admission phase refused run 33896895062 with `30 consumed admission(s) due for deletion on this roster-touching change`. Deletion of consumed rows is charged to whoever next touches the roster, and that is this change. IDENTITY JOIN, NOT A COUNT. The run emitted exactly 30 `CONSUMED ADMISSION` lines, every one naming gunbc#10355 and its own binding -- `gunbc.scm.authoring:: author_requirement `Proposal` -> gunbc.scm.proposal ... already satisfied at the base`. The roster carried exactly 30 rows labelled SCM_PROPOSAL_VOCABULARY_LABEL. Thirty reported, thirty labelled, thirty deleted; the two gunbc#10324 rows are the whole remainder, and their own trigger is this PR merging, so they are not yet consumed. The label constant goes with its rows, per the TWENTY-NINTH's precedent for gunbc#10358's cohort. THE DELETION PASS ASSERTED `examined == kept + deleted` AND THAT ASSERTION EARNED ITS KEEP TWICE. The first parse anchored on `&[` and matched the TYPE ANNOTATION `&[TransitionAdmission]` rather than the array literal, so it examined a 19-character body and found zero rows -- which without the assertion would have been a silent no-op reported as success. The second failed a census check because grep counts `pub struct TransitionAdmission {` alongside the rows: the roster held 32, not the 33 an earlier commit message on this branch stated, and main contributed 30 rather than 31. Both are recorded in the THIRTIETH DISSOLUTION entry so the off-by-one is not inherited by whoever counts next. This pays the ONE blocker of the three on this head that is mine. The other two -- undeclared regen drift on v1_compiler_infer.rs, and a stale-quarantine row that #10402 greened without unenrolling -- are main's, present on main's own push run, and fixed by #10450. This commit is deliberately not pushed on its own: the head would still fail on those two, and cancel-in-progress is false, so it would cost two runs rather than one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * A refusal that dropped the drift it was handed: name what diverged, not just the missing writer Review 60298, upheld. `world_repo_ruleset_apply` took `_: List<RulesetDivergence>` and discarded it, so a name, enforcement, target or required-context drift reached `verify()` as `RepositoryRulesetActuationRealizationUnbound` -- a true sentence about the tree that says nothing about what drifted. The failure STATUS survived and the LOCATED CAUSE did not. §5 requires typed AND located; §3 requires the replacement to preserve every refusal the thing it replaced produced, and the base verifier ended `NotConverged { reason: render_divergences(ds: divergences) }`. It bit hardest on the read-only path, whose entire question is WHAT drifted. THE OBVIOUS FIX IS A NO-OP AND ONLY THE WITNESS SHOWED IT. Both the review and an independent reviewer prescribed carrying the divergences into the `RepositoryRulesetActuationAdmitted` arm. That typechecks, resolves clean, reads correctly, and changes nothing observable: the sibling claim `an_empty_roster_reaches_the_unbound_realization_cause` asserts the admission returns `Refused(RealizationUnbound)`, NOT `Admitted`, so RealizationUnbound is produced by the ADMISSION KERNEL and passed straight through. The arm named by the remedy is unreachable. The witness went FAIL on that version and PASS on this one, which is the discriminating RED and the positive control on one class. WHAT ACTUALLY ROUTES: when the only objection is the unbound writer, drift is the answer; a genuine hold still wins. `repo_ruleset_hold_is_only_the_unbound_writer` enumerates all seven causes rather than using a wildcard, so a hold added later must be classified by hand instead of silently joining the drift branch. HOLDS DELIBERATELY STILL WIN, which is where this departs from the review. A hold means the admission question could not be answered safely at all -- an unobservable bypass roster, an unexpected live rule, a signed policy this projection cannot carry. Those are typed and located about their own subject, and answering with drift instead would suppress a safety refusal to answer a question nobody asked. §5 forbids losing the located cause, not ordering two located causes. Recorded in the annotation, not left as a silent deviation. `repo_ruleset_drift_actuation` is split out because the handler adapters take the context as `_: Bool`, which no witness can supply by name -- the same split `world_repo_ruleset_observation` already carries behind its adapter. The decision was reachable only through a live read, which is how the discard survived four reviews. Seven witnesses PASS locally, six of them pre-existing and unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * Pay the roster-touch obligation #10350's merge created, and regrain the read-only trigger FLOOR BLOCKER, from required run 33908433503: `2 consumed admission(s) due for deletion on this roster-touching change`. Both `kernel-identity predicate relocation gunbc#10350` rows were consumed by their own merge into this branch's base. Their entry named this roster's next touch as when the deletion comes due; this change is that touch. Deleted, with KERNEL_IDENTITY_RELOCATION_LABEL. Adjudicated by the declaring-module join rather than the trigger sentence, on that entry's own rule: `resolved_node_is_kernel_identity_for_name` is declared exactly once in main's tree, at module v1.std.core, and both named callers still reference it -- so base and head bind the same declaring module and the delta is unproducible. The join was calibrated: a fabricated name answers NOT DECLARED against that module while `kernel_span` answers DECLARED, so both verdicts were reachable. Deletion pass asserted examined == kept + deleted (4 = 2 + 2) against a brace-depth parse, not a grep. The claim in the TWENTY-FOURTH TRANSITION that the merged array is four rows is corrected in place rather than left to rot, since it is now false. ALSO, from review 60315's non-blocking remark. It observed that WorldApplied is never constructed. The type stays: it is a variant of the subject-agnostic WorldActuation shape, and that no currently-bound subject constructs it is a fact about repo_ruleset's binding, not about the shape -- deleting it would leave the generic fold unable to express a successful actuation at all. But the remark points at a real §4b(3) defect it did not name: the read-only standing named #10204, an ARTIFACT, as its trigger. Both sites now name the CAPABILITY and what that change must be SUFFICIENT FOR -- a write preserving the live bypass-actor roster AND a post-apply read-back observing it survived -- and say explicitly that #10204 merging is not itself the trigger. This is the grain whose absence this same module already records paying for once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * File the failure mode the &[ near-miss exposed: an edit pass that matched nothing reports success An edit pass whose locator binds to the WRONG CONSTRUCT finds zero subjects, changes zero bytes and exits zero -- and a pass that acted on nothing is indistinguishable from one that correctly had nothing to act on. SPECIMEN, from this PR. Paying the consumed-admission obligation, a pass anchored the roster array on the literal `&[`. That substring occurs FIRST in the declaration's TYPE ANNOTATION, `&[TransitionAdmission]`, so it bound to a 19-character body, found zero rows, and would have reported the obligation paid with an EMPTY DIFF -- after which the wall refuses the next push for the same two rows, against a branch whose own entry claims to have deleted them. Filed rather than folded into empty_capture_read_as_clean_result, which is the nearest sibling: that row covers an empty CAPTURE, and this is the same class moved from reading to WRITING. The move is what makes it worse. An empty capture gives a false all-clear about someone else's artifact; an empty edit gives a false all-clear about an obligation THE AUTHOR OWES, so the debt stays on the roster while the commit message says it was paid. The discriminator is a denominator assertion, not a louder locator: assert examined == kept + deleted against an INDEPENDENT count. Independence is load bearing here -- a grep of `TransitionAdmission {` over the same file answers one HIGHER than the roster because it counts the struct definition, so the cross-check must be a different instrument rather than the same one re-run. CEILING mechanically preventable, with the trigger at capability grain: a shared edit-pass harness that takes the subject population as a REQUIRED input and refuses on a zero or mismatched match count. An author remembering to write the assertion does NOT retire the row -- that is the state this specimen was already in, and it held only because this branch had been burned once already. docs/design-failure-modes.md regenerated by the sanctioned producer named in the workflow's own refusal (generated_artifact_gate main_wet): 2 insertions, 0 removals, no unrelated artifact drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * chore: regenerate drifted generated artifacts (ci auto-heal) * Pay the roster-touch obligation a third time: delete gunbc#10439's six consumed serving-engine rows FLOOR BLOCKER on the merge head: `6 consumed admission(s) due for deletion on this roster-touching change`. gunbc#10439 merged, so all six `serving engine launch vocabulary move` rows were consumed by their own merge, and the deletion falls to this roster's next touch. That is this change, which inherited the rows by taking main's file whole in the preceding merge. ADJUDICATED BY THE DECLARING-MODULE JOIN, not by the trigger sentence. All three spellings the rows name -- spark_serving_bind_listen_wire, SparkServingBindListen, OllamaServingLaunchProfile -- are declared in gunbc.spark.serving_engine on main, so base and head bind the same module and no run can produce these TargetChanged deltas. Calibrated: a fabricated name answers NOT DECLARED against that same module, so a uniform "found" was not available to the join. Deletion pass asserted examined == kept + deleted against a brace-depth parse, 8 = 2 + 6. The two gunbc#10324 rows are the whole remainder. THIRD TIME ON THIS BRANCH IN ONE EVENING, and it authored none of them: 30 gunbc#10355 rows, both gunbc#10350 rows, now these six. Each arrived the same way -- another PR merged, its rows became consumed, the debt attached to whoever next touched the file. Recorded in the entry because the roster is where the cost lands, and no author action avoids it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md edit_pass_that_matched_nothing_reports_success Ledger-Repair-Judged: docs/design-rung-drops.md * chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md edit_pass_that_matched_nothing_reports_success Ledger-Repair-Judged: docs/design-rung-drops.md * Pay the fourth consumed-admission obligation: 22 gunbc#10445 rows go dark THE WALL NAMED THEM, AND IT NAMED ALL OF THEM: required-ci: FAILED PHASE namespace-wave-admission (0 unadjudicated delta(s), 0 stale admission(s), 22 consumed admission(s) due for deletion on this roster-touching change) gunbc#10445 merged between this branch's last two heads, so its 22 rows -- 5 node_target_of constructor rows and 17 object-table codec rows -- were consumed by their own merge, and the deletion falls to the next change that touches this roster. That is this one. Both label constants go with the rows they labelled. ADJUDICATED BY THE DECLARING-MODULE JOIN, NOT BY THE TRIGGER SENTENCE. Every spelling the rows name is declared in the module the row gives as its target: node_target_of in gunbc.scm.object_store, and object_id_key / encode_target / EncodeAcc / DecodeAcc / DecodePositions / decode_object_table / encode_object_table / node_position_of / resolve_reference in gunbc.scm.object_table_json. Base and head bind them identically, so no run can produce these deltas. CALIBRATED, NOT TRUSTED: a fabricated spelling through the same join answers NOT DECLARED against the same module, so a uniform "found" was never available to it -- without that control the join could have been reporting its own success. THE PASS ASSERTED examined == kept + deleted AGAINST A BRACE-DEPTH PARSE: 24 = 2 + 22. Not against a grep, because `TransitionAdmission {` also matches the struct definition, and an earlier pass on this branch anchored on `&[`, matched the type annotation `&[TransitionAdmission]`, examined a 19-character body, found zero rows, and would have reported the obligation paid with an empty diff. That class is filed as edit_pass_that_matched_nothing_reports_success -- whose roster row this same branch adds. FOURTH TIME, 60 ROWS, NONE OF THEM AUTHORED HERE: 30 from gunbc#10355, 2 from gunbc#10350, 6 from gunbc#10439, 22 from gunbc#10445. No author action avoids it; it is a queue artifact, and the roster is simply where it lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EdHQ244XXdBKRKGX6jrV4 * chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md edit_pass_that_matched_nothing_reports_success Ledger-Repair-Judged: docs/design-rung-drops.md * chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md edit_pass_that_matched_nothing_reports_success Ledger-Repair-Judged: docs/design-rung-drops.md --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The leftover unsigned / no-merge_queue / nothing-claims-to-write prose was an attractor beside the shipped actuator. #10204 remaining grain is a rules write that does not replace an unread bypass roster. Co-authored-by: Cursor <cursoragent@cursor.com>
gunbc.repo_rulesetstates thatstrict_required_status_checks_policyis false because the merge queue is the construction that establishes the same invariant without re-running the floor for every PR behind a merge, and that the queue is not configured yet.gunbc.witness_floor_workflowalready lists themerge_grouptrigger. Nothing declared the queue itself, so "turn it on" was a web-form change nobody in this repository could see — the exact condition the ruleset module exists to end for the required context.No live repository setting is touched by this PR. The live change is gated on operator sign-off, and the gate is a type, not a comment.
What lands
1.
extdeps.github.rulesets— themerge_queuerule. TheMergeQueueRuletag, the two SCREAMING wire enums (grouping_strategy,merge_method) parsed into closed coproducts with unrecognized arms, andMergeQueueStandingwith four states: absent, configured, parameters-incomplete, and more-than-one-refuses.GitHub sends one
parameterskey whose object differs by rule type. The shape that says so is a coproduct discriminated on the type tag — and it is unreadable here: the REST realization decodes a nested object into an untagged map, so a coproduct arm has no constructor tag to match and every read refuses. What lands is the union of the readable shapes, with the guard written down as load-bearing (every reader filters on the rule type before touching a field) and a next-rung trigger stated as a capability: a transport decode that carries a discriminator from the enclosing object into a tagged value. The merge_queue fields are optional, so a rule whose parameters arrive incomplete refuses and names what is missing rather than reading zeroes.2.
gunbc.repo_ruleset— the proposal, with the sign-off in the type.MergeQueueDesire = MergeQueueUnsigned | MergeQueueSignedOff, only the signed arm yields a policy. Desired state here is actuated — converge PUTs the whole ruleset — so writing the queue intodesired_ruleset_ruleswould enable a merge queue on a live repository on one worker's edit. UnderUnsignedthe settings are declared, readable and diffed, and contribute no rule to what converge sends. The row also states that a UI-enabled queue rule would be deleted by the next converge: not a shortcut past the model, a change with a scheduled silent self-reversal.MergeQueuePolicycarries the complete documented parameter set — a Boolean would say "queue on" while seven unmodeled parameters decided what the queue does. Every value is derived rather than picked: ALLGREEN because HEADGREEN re-opens the joint-failure class; SQUASH because the repository allows squash only;check_response_timeoutread fromwitness_floor_lane_timeout_minutesplus a declared margin, because a queue timeout below the repository's execution envelope dequeues green work for being slow; entry counts commissioned at 1, with the rise to 5 as aDissolutionConditionretired by receipts showing the one-entry protocol converging.A hole closed while modelling it. The divergence fold only asked "is every declared rule present". The PUT is a whole-ruleset replace, so a rule that exists live and is absent from desired was deleted with no divergence reported before or after. Applying desired is what destroys it, so
convergenow refuses rather than writing.verifystill reports it.3.
gunbc.merge_queue_admission— what a queue must satisfy. The exact-subject identity (the checked composition is the commit that lands), the ordering chain (entry 0 onto the observed target tip, entry N onto entry N-1 — what breaks when main moves under the queue), and the grouping strategy.The two terminals are separate constructors, and the evidence for each is separately carried.
ReaderBypassStandingis a fact about the reader (current_user_can_bypass);BypassActorRosterObservationis a fact about the ruleset. An absentbypass_actorskey isBypassRosterUnavailable, never empty — GitHub omits it both when the roster is empty and when the reader may not see it — and there is no path from Unavailable to Exclusive, so an unauthorized read cannot mint the strongest claim. Why a queue is not exclusive is itself a coproduct: always-bypass actor, pull-request bypass actor, roster-not-observed — three different remedies.Two silent destructive writes, closed by the same argument
Converge sends the whole ruleset, so anything live that the projection does not declare is deleted by applying desired. The divergence fold only asked "is every declared thing present", which is the wrong direction for this class entirely: a divergence means the world is not what we declared, apply desired, and here applying desired is what destroys the thing. Both halves now refuse rather than repair, and
verifystill reports, because reporting is what verify is for.rule_unexpected_divergences+merge_queue_projection_refusal.bypass_roster_projection_refusal.desired_ruleset_rulesprojects nobypass_actorsat all, and this repository has an observed always-bypassRepositoryRoleid 2, so an apply today would have deleted a live grant with no divergence reporting it. This half was open while the rule half was closed; the two were written days apart and the asymmetry was the defect, not the actor.The unreadable arm is the one that matters most:
BypassRosterUnavailablemeans GitHub omitted the key — indistinguishably because the roster is empty or because this credential may not see it — so an apply would delete either nothing or an unknown number of grants, and the observation cannot say which. Treating unavailable as empty would let the empty-observation narrow decide a destructive write. It refuses. Three witnesses, including a positive control on an observed empty roster, without which the check could be "always refuse" and both REDs would still pass.Unreadable does not render as converged, and that is load-bearing
observation_divergencesreturns[]for aRulesetUnreadableobservation. So a path that reached the divergence fold without guarding would compute zero divergences and reportConverged— unreadable rendering as converged, the absorbing fallback in its purest form, deciding a live write.Both entry points match
RulesetUnreadablefirst:repo_ruleset_converge_actuate_atandverifyeach returnNotConvergedcarrying therender_read_refusalcause. This is checked rather than assumed, because it is the one class this module's own carriers claim to guard against —RulesetUnreadableis a distinct constructor rather than an empty observation, exactly asBypassRosterUnavailableis. The guard is what stands between this module and that failure, not an incidental ordering.Live read-back
Ruleset
16178731, read 2026-09-03: rules aredeletion,non_fast_forward,required_status_checks→merge_queue_standing=NoMergeQueueRule, no divergence under the unsigned desire, the actuation refusal does not fire, terminal =Unconfigured. The repository allows squash only (allow_merge_commit/allow_rebase_mergefalse).Bypass: settled, in the positive direction. Three unprivileged reads returned no
bypass_actorskey andcurrent_user_can_bypass: never— an unavailable observation, not an empty one. A privileged observation then reported an always-bypass actor:RepositoryRole, id 2. So Exclusive for this repository is not merely unproven, it is false while that grant stands, and a witness asserts that this observed roster holds the terminal at Operational.The documented actuator route
gunbc run --entry dag/gunbc/repo/repo_ruleset.dag --function verifydoes not execute today: ~3124 resolution diagnostics, never reachingverify. Measured identical onorigin/mainin a detached worktree, so it is pre-existing; the live read above went through the same REST endpoints the model declares.The bounded gap
Nothing here proves the required aggregate executes on a real
merge_groupcomposition, because no queue exists to compose one. The in-repo half is witnessed from the workflow authority. The world half is aDissolutionCondition, not a caveat: it dissolves on the signed flip plus one observedmerge_grouprun whose subject commit equals the composition GitHub fast-forwards.A cost shape found by this lane's own red
Five of these witnesses were reported
budget_interruptedby the required floor's per-claim budget — which is notclaims_failed: the PR goes red with a disposition naming no failing claim. The cause was not the claims.floor_workflow_admits_merge_groupread the fieldwitness_floor_workflow.on, forcing the entireWorkflowvalue — every job, step, matrix and env row of the required floor emission — to answer a membership test over a four-element list. The narrow producer already existed beside it:witness_floor_triggers(), which the workflow's ownon:is literally bound to, so reading the function is not a copy of the authority but is the authority.Every column below is located by name from
required_floor_claim_cost.tsv(identity | module | outcome | verdict_reached | wall_ms | cpu_ms | eval_steps | cost_line_ms), before from run33750639005, after from33754056276:w_the_required_workflow_can_schedule_for_a_compositionw_a_configured_queue_with_an_observed_empty_roster_is_exclusivew_RED_an_unavailable_bypass_roster_cannot_mint_exclusivew_RED_an_always_bypass_actor_holds_the_terminal_at_operationalw_RED_the_two_grants_and_an_unreadable_mode_do_not_collapseThe
outcomecolumn carries the verdict without arithmetic: all five moved frombudget_interruptedtopass. Evaluation steps fell by roughly three orders of magnitude and measured CPU fell below the millisecond the artifact reports in.Which quantity the budget actually judges is under active investigation by the FLOOR-COST-500MS lane and is not settled — they report a 744 ms claim passing while a 508 ms claim was interrupted a run earlier, on the theory that charged
cpu_msincludes shared-artifact fill that the deadline nets out. This section therefore rests on theoutcomecolumn, which is the floor's own verdict and needs no threshold, rather than on any claim about a millisecond line. Thecpu_msandeval_stepsfigures are reported as what the artifact says, not as a distance from a limit.An earlier revision of this section reported the "after" column as 35–154 ms and claimed 3.2×–14× under the line. Those figures were
eval_steps, read by column index where index 7 iseval_stepsandcpu_msis index 6.gunbc.floor_cost_distributiondocuments thateval_stepswas inserted between artifact vintages precisely so that an index-keyed parser reads the wrong column, and DESIGN §3's locate-by-name rule is the stated remedy — which I had cited in this same PR before misapplying it. A derived "~93 % carrier share" was arithmetic across two units and is withdrawn entirely rather than restated. The mechanism claim is unchanged and the true reduction is far larger than the one withdrawn.Scope
Not in this PR, deliberately: any live ruleset write, CI cost redesign, runner expansion, the MAIN-6 cutover.
gunbc.merge_queue_admissioncarries a rebind-or-delete condition on MAIN-6, and states as a non-goal that a queue composes trees and cannot compose two edits to one authority row — the one-row/one-writer rule stands.🤖 Generated with Claude Code
https://claude.ai/code/session_01RPZR4RfT74eH25bQQZzFKL