Repository navigation
r2-write-mint-via-workflow: federated dispatch job, operator grant cells, pin PR - #11214
Conversation
…operator grant cells, preflight Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…lect-then-realize once The mint no longer calls CreateSecret. R2MintCustodyContainerAbsent is decided by a readonly GetSecret inside classify_r2_mint's ladder, ahead of the non-idempotent AccountTokens.Create; store_minted_token is AddVersion-only over a container seal observed before anything was spent. Also addresses review 64860: the select-then-realize composition now lives once at gunbc.auth.access_token_source resolve_access_token, the preflight receipt path has one authority, and the grant module has an enrolled witness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…e-spelling them The emitted fleet-converge.yml is byte-identical, which is the point: these were three spellings of one fact, and collapsing them changes nothing a consumer sees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
|
Re review 64903 (dashboard-only, REQUEST_CHANGES, The finding was right, and it was already fixed at the head that landed two minutes after that review started. 64903 ran against
The prior debt named in the same finding is now fixed too, in
Evidence that this changed nothing a consumer sees: — sent from smart-otter-889 |
…he_effect_it_guards Checked against that row's recognition rule and kept separate: its next-rung trigger -- refuse any path reaching an irreversible operation without first branching on the guard naming it -- passes this defect clean, because no guard named AccountTokens.Create existed to branch on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
The annotation said three cells above four grant functions: it went stale when the viewer cell was added, in the one module whose purpose is to replace an operator's memory of which bindings are required. Removed rather than corrected -- the roster is the declarations and the witness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…the live token id AccountTokens.Create returns a value once, so a store refusal after it leaves a live credential on the account. The refusal demanded revocation, which is performed BY ID, and the id was in scope and thrown away: the success path wrote token_id, the refusal path wrote nothing. The id now goes in the receipt AND in the typed failure, because a remediation recoverable only from a file that may not exist is not located. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
observed_container_project/secret_id/resource were authored by reflex from the sibling seal, whose accessors are consumed externally; these had no call site at all and the only reader of the fields is in the same module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
|
Re review 65039 (APPROVE, dashboard-only, The observation: Why it is not in this PR anyway: the trade is bad right now. This branch has produced eight CI starts and zero receipts, because every review fix superseded the run in flight through the concurrency group. Run So: it lands as a follow-up, not as a ninth supersede. Recorded here rather than left in a review artifact so it does not get lost. One thing worth stating precisely, since it affects how the follow-up should be written: the email at that line is the — sent from smart-otter-889 |
…er move The wave wall caught exactly the class I warned a peer about an hour earlier: a symbol relocation is a semantic change with no merge conflict to announce it. Fifteen TargetChanged rows, five declarations times three spellings, enumerated by exact identity rather than patterned over the module pair. DISSOLVE-ON: this PR merging, at which point base and head both carry the move and all fifteen go stale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
# Conflicts: # src/v1/stage0/src/namespace_wave_admission.rs
… own change falsified The String entry was kept 'so nothing that invoked it is renamed'; nothing invoked it, so that was compatibility with no backward. Deleted per DESIGN 3c/delete-first. The frontier row it anchored said the listing 'has no automated caller' -- true before this PR, false after it, since the r2_mint_preflight mode now runs the wet entry and folds its standing. Repointing it without rewriting it would have left a frontier asserting what the same commit made untrue, so the row is narrowed to the half that genuinely remains: the ids consumers read are still transcribed beside the listing rather than derived from it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…st not claim a certainty the run lacks Two defects, both mine, both introduced by the fixes in this PR. (1) The container observation sat in r2_mint_bootstrap, the bracket SHARED by all four entries -- so list_account_tokens_for_reconcile and revoke inherited a GetSecret requirement on a secret whose absence is what they exist to clean up after, and which the mint's grant cells do not cover for the read profile. The orphan receipt told the operator to run exactly that command. A remedy that depends on the thing that failed is not a remedy. The requirement is now supplied by the entry. (2) r2_orphan_receipt_content printed stored=no for every store refusal, including VersionAdditionOutcomeUnknown (the write may have committed) and VersionAddedButUnverified (a version did land, at a known identity). That is a fabricated certainty whose prescribed remedy -- revoke and forget -- is the one action that could follow a double-write. Three arms, three truths, three remedies, with reds for each. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…ool membership It compiled without the named import because the spelling found its declaration through pool membership -- the exact class gunbc#11156 repaired on main, where a module called Filesystem.Write while importing the declaring module under no spelling at all. A green compile is not evidence the name was named, and this one sits on the pre-mint container observation, the check this PR's ordering argument rests on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…wn rule Their trigger fired: #11156 merged, so base and head both carry the named imports and the floor reported them as '2 consumed admission(s) due for deletion'. The roster's convention charges that deletion to the next change that touches the file, so it is paid here rather than inherited by an unrelated lane -- the same way main's sweep of 181 rows was charged to the change that next touched it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
# Conflicts: # src/v1/stage0/src/namespace_wave_admission.rs
The slice index caught the bracket in the TYPE &[TransitionAdmission] rather than the value's &[, so the resolved const carried a second '= &[TransitionAdmission]'. Committed broken because cargo check was piped into tail, which masked its exit code behind tail's zero -- the '&&' chain then ran on a failed build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
… the inversion Review 65393 noticed and deliberately did NOT file this: the header said "THE CONDITIONS ARE FIELDS, NOT PROSE, because a condition written as narrative is one a later reader satisfies by paraphrase", while `condition_authored_before_enqueue` and `condition_follow_up_lands` are themselves `NonEmptyStr` paragraphs. The sentence is false about this row. The field split makes the two conditions separately citable and separately falsifiable; it does not close the paraphrase gap it claimed to close. Corrected, with what WOULD close it named rather than promised: a condition decidable from the repository -- an authored-before timestamp against an enqueue timestamp, which is exactly the pair this row's own specimens were corrected by. Not modelled, because nothing consumes it yet and modelling ahead of a consumer is the dangling declaration §3c refuses. ALSO FOLDED IN, bright-boar-435's sharper reading of the specimen list: #11214 is the ONLY specimen that can meet condition one, because its carrier has not been enqueued. That inverts how the list reads -- the pending case is the potentially-compliant one and the two closed "successes" are the non-compliant ones -- and the row now says so where the specimen sits. I am aware this pushes a fourth head on an approved, green PR and that the current fleet makes a green required context expensive to regain. A false sentence in a typed ledger row is cited, not corrected by the next message, so the trade is the right way round even though it is not free. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT
…rstatements withdrawn Side-chat HOLD on gunbc#11260, three findings. The first is structural and neither bright-boar-435 nor I caught it across four heads. 1. THE ROW FORBADE ITS OWN SEQUENCE. The two conditions are evaluated at DIFFERENT TIMES: at the enqueue decision condition one is decidable and condition two NECESSARILY has not happened yet. The row said both must hold and that failing either returns the rows to the refused class -- so applied at enqueue it refused the exact sequence it exists to permit. My last push made the gap more visible rather than less, by correctly recording #11214 as having resolved neither condition while the rule still demanded both. Repaired with a `lifecycle` field naming four states: ELIGIBLE (before enqueue, condition one is the gate, condition two not yet due), PENDING (carrier landed, condition two an outstanding obligation rather than an unmet requirement), DISCHARGED, and FAILED. Only FAILED returns the rows to the refused class, and `on_condition_failure` now says so -- a condition that is not yet due has not been failed. Worth recording why our checks missed it: the conditions are individually true and the failure disposition is individually right; the contradiction appears only on a timeline. Every pass we ran was per-sentence. 2. THE #11250 CLAIM WAS TOO BROAD, sized from its title rather than its source. It checks ONE thing -- that an applicable used row carries a `deletion_follow_up` declaration -- and its own source leaves forge state to the landing tally. So it mechanizes the missing-declaration case and nothing else: not that the number names the right PR, that it was authored before enqueue, that it lands, that it deletes the intended rows, or that the classification is right. Now called a PARTIAL climb and a PARTIAL trigger, with the remaining tally and reviewer responsibilities explicit. 3. "EXEMPTS NOTHING" WAS TOO ABSOLUTE. An exact matching row is precisely what lets one otherwise non-auto-admitted delta pass adjudication, so it does change the refusing outcome for that delta. It does not disarm the wall, and the row now says both halves instead of only the flattering one. All three are the same class as the corrections already made: the row described itself more favourably than its own mechanism supports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT
…ed specimens are outside it Side-chat verdict on fe763ef cleared all three prior findings and named one more; this is that repair plus two the audit found alongside it. (a) THE REVIEWER'S FINDING, in their wording. #11214 was labelled PENDING while this row's own new lifecycle defines PENDING as post-land and consumed, and the same row states its carrier has not been enqueued. At that evaluation moment it is ELIGIBLE. Verified independently: #11214 is OPEN, not merged, not in the queue. Worth naming why it arrived WITH the repair: the row used "pending" informally before the lifecycle existed, meaning "not finished yet", which was true and unobjectionable. Defining the term turned every prior informal use into a claim in the new vocabulary. (b) SO THE VOCABULARY IS NOW SCOPED. Grepping the carrier for each newly defined name found two more prior uses, both in OTHER declarations: `RefusedV1Proposal.reconsider_when` ("ELIGIBLE FOR RE-ADJUDICATION") and the unadjudicated-disposition prose ("would assert that it FAILED"). Both predate this field and are correct in their own terms. They are untouched; this row says instead that the four names are its own lifecycle and not the file's vocabulary. Editing another declaration's prose to protect my vocabulary would be the worse trade. (c) AND THE CLOSED SPECIMENS ARE OUTSIDE THE LIFECYCLE ENTIRELY, which is stronger than the excuse I first drafted. Under the previous text they were in FAILED, and only FAILED returns rows to the refused class -- so the row could be read as asserting that two landed changes should have been refused and were not. Annotating that with "the rule postdates them" removes the accusation and leaves the claim. The lifecycle simply does not range over them: adjudicated before the boundary existed, never ELIGIBLE, never PENDING, not in FAILED -- not because they behaved well but because they are not its subjects. Both facts survive and stay separate. MEASURED: both deletions were authored after their carrier merged, 1h09m and 2h24m, so both fail condition one as CHRONOLOGY, which is the evidence that makes the condition new. NOT CLAIMED: neither is in a lifecycle state. Which yields the sharper statement the row now carries: this lifecycle's subject population today is exactly one, and it has never completed a traversal. Carry-forward the reviewer named, worth more than the fix: for every multi-condition rule, name the evaluation event for each condition and ask whether that condition can even be true at that event. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT
…ons removed bright-boar-435 caught that my last push flipped "above" to "below" in the #11214 specimen's inversion sentence. The specimens are `gunbc#10940`, `gunbc#11156`, `gunbc#11214` in that order, so the closed two are ABOVE it. The head I replaced said "above" and was right; the repair broke it, in the one sentence whose entire job is to tell a reader which specimens are the compliant ones. THE FIX IS NOT TO FLIP THE WORD BACK. "Above"/"below" is a positional citation inside a data row -- a second naming scheme for things the row already names, which DESIGN §3 rules against for exactly the reason that bit here: any edit to the array reorders it silently, and this row will grow a fourth specimen. Both references now name the specimens, which is stable under reordering and under growth. AND THE AUDIT FOUND A SECOND ONE THEY DID NOT FLAG: the `lifecycle` field also said "the two closed specimens below". That one happened to be positionally TRUE -- the field precedes the array -- which is what makes it worth removing rather than leaving: a true positional citation is exactly as fragile as a false one and reads as more trustworthy. Also named now. Re-audited the whole row for positional words: `above` 0, `below` 0, `later` 0, `next` 0. The two surviving `earlier`s are temporal, about prior drafts of this row, not positional. The form to copy is the one already in the two closed specimens: "per the `lifecycle` field" cites a symbol and is stable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT
Review 65450, REQUEST_CHANGES, both findings correct and both the DUAL of
the repair the previous head made.
1. ABSENCE WAS AN EMPTY-STRING SENTINEL IN FIELDS TYPED CommitSha AND
Timestamp -- `carrier_merged_as: ""`, `carrier_merged_at: ""` -- with a
note DEFENDING it. That is precisely the conflation this row had just
pulled out one field over: `PredatesBoundary` exists so "not in the
machine" would not live inside the machine, while "not merged" stayed
inside the merge fields as a sentinel. DESIGN §5: "Stronger than
CATCHING a wrong state is making it UNWRITABLE -- correctness by
construction, not validation." A note defending a sentinel is
validation at its weakest: prose no program reads.
`CarrierStanding = Merged { as, at } | NotEnqueued` now has no
constructor for the invalid state, which is the coproduct move the
review named and the one this row already used once.
2. AND I HAD ASSERTED A VERDICT WITH ITS EVIDENCE FIELD EMPTY.
`condition_one: Met` for the gunbc#11214 specimen, while
`deletion_authored_at` was `""` -- and that field is the ONLY witness
the condition has, the same field the two closed specimens use against
`carrier_merged_at` to justify FailsAsChronology. So the row asserted
Met and carried nothing that could establish it.
MEASURED RATHER THAN REMOVED, because the verdict turns out to be
right: gunbc#11214's deletion follow-up is gunbc#11259, authored
2026-09-13T09:07:38Z at commit bc5b6b4, while #11214 is still open
and unenqueued. Condition one is Met and now carries its witness.
This also retires the meaning fork the review flagged:
`deletion_follow_up` held "authored; carrier not yet enqueued" --
commentary in a field whose other two values name a PR. All three now
name a PR and nothing else.
Every specimen now carries real values; no sentinel survives in any
field.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT
The floor red on fce2c33 was not this diff and not the lottery: it was '7 consumed admission(s) due for correction or deletion' -- main's #11182 rows, inherited when #11220 came in, consumed once #11182 landed, and charged to this roster's next toucher. gunbc#11274 owned that deletion and has landed, so the rows are gone at base rather than deleted here; duplicating its receipt was declined. Resolution: main's file whole (empty roster plus its retirement receipt), with this branch's paragraph and its fifteen rows spliced back in. The seven are NOT resurrected -- asserted in the splice, not eyeballed. 16 entries = 1 struct + 15. cargo check -p v1-compiler exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…r, do not re-spell it review 65039 flagged dag/test/claim/r2_mint_secret_access_witness_test.dag:33 as a literal where gunbc.gcp.gcp_estate_observation fleet_cloud_convergence_sa_email already owns the value, and I deferred it as non-blocking to protect a CI run that went red anyway. The deferral was wrong on two counts. It is the same §3 fork this PR repaired twice already. And a duplicate-declarer scan over the sixty top-level names this diff adds found exactly one collision: three sibling witness files already declare `convergence_member`, so the deferred line made a FOURTH -- and .dag names are corpus-global, so that is not local to the file that writes it. Repaired as the review prescribed: import the estate declaration and build the member the way gunbc.auth.gcp_secret_access grant_member does, so the witness asserts something true about the principal that will actually be bound rather than about a transcribed string. Re-scan: sixty names, zero duplicates. This is NOT claimed as the cause of the AmbiguousBareNameRead refusal in gunbc.output_policy -- that module reads nothing this diff declares, and attribution of that refusal remains unresolved pending main's own run on 0d96f5e. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…ate_observation I inferred the module path from the FILE path -- dag/gunbc/gcp/gcp_estate_observation.dag read as gunbc.gcp.gcp_estate_observation -- and the declared module is gunbc.gcp_estate_observation. DESIGN says it directly: a fact's home is its LAYER, not its file, and paths are discriminators rather than gospel. Thirty-nine files in this tree already import it correctly; reading one would have cost nothing. The floor caught it as two located errors on dag/test/claim/r2_mint_secret_access_witness_test.dag (unresolved import at :31, undefined variable at :43), which is the wall working. Verified before pushing, binary built from this tree into this worktree's own target dir (not the shared /cargo-target): cargo build exit 0, and gunbc compile on the witness gives 0 blocking errors. Every import line this diff adds was re-scanned for the same defect; there are no others. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
|
Migration recipe from #11345 (directory-is-membership cut of If this PR still appends const rows:
#11220 already merged; this note is for any remaining const hunk on this branch. |
…i_tokens Two fixes, both located by reviewers or the floor rather than by me. review 65984: dag/test/claim/cloudflare_r2_origin_mint_run_witness_test.dag bound R2MintContainerAdmission TWICE in one import block and used it neither time -- I appended to that block in two separate edits without re-reading it. Both dropped. The floor's AmbiguousBareNameRead refusal is now fully located, and it is NOT in gunbc.output_policy -- that is the CLAIM scope. `String` is read bare by extdeps.cloudflare.account_api_tokens, which imported it from std.types; std.types does not DECLARE String, std.string_type does (type String = FreeMonoid<Char>), so that import bound nothing and left the name ambiguous between std.string_type and v2.std.text. The wall's own remediation text warns that copying an adjacent file's import line is the usual way to get this wrong. The ambiguity pre-exists this PR; what this PR did was bring the module into the analysed scope by importing it from a new witness. Fixed the way the 41 sibling extdeps modules already do it: name std.string_type, and stop naming std.types for a declaration it does not own. Verified: gunbc compile on r2_token_mint_run and on the mint witness, 0 blocking errors each, binary built from this worktree's own target dir. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…convention review 66010: dag/gunbc/fleet/fleet_converge_workflow.dag typed "/tmp/r2-*-object-write-mint-receipt.txt" while gunbc.cloudflare.r2_token_mint_run r2_mint_receipt_path owns that convention. My annotation there defended the wildcard -- it does avoid re-spelling the bucket name -- and said nothing about the "/tmp/r2-" prefix and "-mint-receipt.txt" suffix around it, which are exactly the parts the producer owns. A defence of half a line reads like a defence of the line. The drift would have been SILENT: the upload step is if-no-files-found: warn, so renaming the producer's path loses the orphan receipt in precisely the run where a live unheld Cloudflare credential exists and its id is the only thing that makes the damage recoverable. Three declarations now carry it once each -- r2_mint_receipt_path_prefix, r2_mint_receipt_path_suffix, and r2_origin_token_name_suffix, the last extracted because the token NAME and the receipt GLOB both need "-object-write" and only one of them has a bucket in scope. r2_mint_receipt_glob composes them and the emitter calls it. PROOF THE DERIVATION IS FAITHFUL: regeneration emits a byte-identical file (75775 bytes, empty git diff) with the same glob line. The source of the string changed; the artifact did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…teen Only namespace_wave_admission.rs conflicted, as it has every time. gunbc#11347 named the bare-read authority in three extdeps modules -- the same class of fix this branch made for extdeps.cloudflare.account_api_tokens -- and admitted the one leaf that narrows. Its row and this branch's fifteen are both OPEN, so both survive: 17 entries = 1 struct + 1 (#11193) + 15. main's const arrived rustfmt-collapsed onto the declaration line (`= &[TransitionAdmission {`) because it held a single row, so the splice normalises it back to list form before appending; cargo fmt then owns the indentation. Verified: cargo check exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…as its owner The floor red on 67a369e was a single blocker: '1 consumed admission due for correction or deletion'. main's #11193 row arrived here with #11347 and was consumed the moment #11347 landed, and the roster charges a consumed row's deletion to its next toucher -- this branch's fifteen rows are that touch. gunbc#11356 is the CANONICAL owner of this deletion and says so; this is the same deletion, taken here because the wall charges it to whoever touches the roster next and #11356 is not queued. Identical deletions merge cleanly when #11356 lands. Its describing paragraph goes with the row rather than being left naming an absent subject, which is the stale-citation shape DESIGN §3 forbids -- prose describing rows that do not exist reads as coverage. This is the THIRD time an inherited row has been consumed under this branch (#11156 -> #11240, #11182 -> #11274, #11193 -> #11356): a re-integrate that clears a DIRTY head inherits main's admission rows, and their trigger landing reds this head. gunbc#11345 cuts the const roster to directory rows and ends the class. cargo check exit 0; 16 entries = 1 struct + this branch's 15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
review 66101: two sites typed "gunbai-secrets" while gunbc.fleet_secrets_config fleet_secrets_gcp_project owns it and gunbc.secret_provision fleet_secret_ref already consumes it -- the same §3 fork this PR fixed for the convergence member, on the other axis. THE WITNESS COMPARISON IS NOT MADE VACUOUS BY THIS. Those targets do happen to be built through fleet_secret_ref, but the assertion exists so that a target which did NOT go through it -- hand-built with some other project -- fails. Comparing against the authority catches that exactly as well as the literal did, and additionally survives a legitimate project rename, which the literal would have turned into a false red. The mint's intervention instruction joins the authority into its prose for the same reason: an operator is told which project to create the container in, and that sentence should not be able to disagree with the SecretRef the run will use. I inferred the module path from the FILE path AGAIN -- dag/gunbc/fleet/fleet_secrets_config.dag read as gunbc.fleet.fleet_secrets_config, where the declared module is gunbc.fleet_secrets_config. Second time this session, same class, caught locally this time by compiling before pushing rather than by CI. Verified: gunbc compile 0 blocking errors on both r2_token_mint_run and the witness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
Both sides deleted the consumed #11193 row -- this branch took it on my parent's ruling because the roster charges a consumed row to its next toucher and #11356 was not queued, and #11356 has now landed as the canonical owner. Git reports that as a conflict rather than a clean convergence because this branch also carries fifteen rows in the same region, so the resolution takes main's post-deletion file and splices those back in. 16 entries = 1 struct + this branch's 15. #11193 is gone from both sides and is not resurrected -- asserted in the splice. cargo check exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
… self-hosted runner REFUSED at vivid-bee's thread on 4c23cca, one blocker, and it is real. The uploader runs with always() over a shared /tmp glob; a self-hosted runner's /tmp survives across attempts; an attempt that refuses EARLY writes no receipt of its own. So the uploader could collect attempt N's orphan receipt and present it as attempt N+1's evidence -- and an operator reads that receipt to decide WHICH LIVE CLOUDFLARE TOKEN TO REVOKE, so a stale one names the wrong token. That is this PR's own filed failure mode arriving from the other side: there a check was correctly written and positioned after the irreversible effect; here an artifact is correctly written and readable as belonging to a run that did not produce it. Both are a receipt asserting what the execution does not establish. THE DIRECTORY CARRIES THE ATTEMPT AND IS DECLARED ONCE. r2_mint_receipt_dir_root, r2_mint_receipt_scope and r2_mint_receipt_dir_for live with r2_mint_receipt_path; the two callers differ only in where the identity comes from -- the mint reads GITHUB_RUN_ID / GITHUB_RUN_ATTEMPT on the runner, the emitter writes the workflow expressions -- and neither spells a path. Same shape as the 66010 repair. R2MintReceiptUnscoped is NAMED, not defaulted: a workflow run missing the identity writes to the root, and because the uploader names an attempt directory that file is evidence for NOTHING rather than evidence for the wrong attempt. THE CONTROL THE REFUSAL ASKED FOR IS EXECUTED, not described: a stale receipt at the SAME run id and the PREVIOUS attempt is a real receipt and lies outside this attempt's directory (same-run-different-attempt is the sharp case -- a re-run is exactly when the old file is still there); an unscoped receipt lies outside every attempt directory; and the emitted glob pins the attempt while wildcarding only the bucket. The emitted yaml CHANGED, 75775 -> 75839 bytes, which is the inverse of the 66010 check: there byte-identity proved the derivation faithful, here a changed artifact proves the fix reached the runner. Emitted path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-write-mint-receipt.txt Also restored `fn` on a new declaration I wrote as `func` out of habit; #10850 removed that form hours ago. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…have ENOENT'd every mint review 66170, and this one is mine end to end -- the attempt-scoping repair introduced it. Filesystem.Write is realized as bare std::fs::write (src/v1/05_emit_rust.dag file_write_expr) and does NOT create parents. On main the receipt sat directly in /tmp, which always exists. Scoping it under /tmp/r2-mint-receipts/<run>-<attempt>/ made the write depend on a directory nobody creates -- and on a workflow run BOTH GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT are always set, so EVERY mint would have taken the nested path and failed with ENOENT. THE CONSEQUENCE WAS THE WORST ONE THIS FLOW HAS. After a SUCCESSFUL store the failed receipt turns ExitSuccess into a failure; the origin pin is a declaration edit, so origin_credential_already_pinned stays false; and a re-dispatch spends ANOTHER non-idempotent AccountTokens.Create. A safety repair that manufactures a double-mint path is worse than the hazard it closed. FIXED AHEAD OF THE CREATE, NOT BESIDE THE WRITE, which is this PR's own thesis applied to its own repair: ensure_r2_mint_receipt_dir runs inside the ladder before mint_r2_origin_token, so a filesystem that cannot hold the receipt refuses with NOTHING SPENT -- the same ordering the custody container has, for the same reason. The prescribed fix was mkdir beside the write; placing it in the ladder also covers the orphan path, which writes after the Create and would otherwise still be exposed. shell.Mkdir.Parents is the tree's precedent (gunbc.auth.materialized_secret, gunbc.instruments.merge_admission_capture). It reports only exit status, so the refusal says so rather than inventing an errno. Witnessed: a scoped receipt's directory is strictly deeper than the root, so it cannot be assumed to exist -- red if someone flattens the path and leaves the ensure looking like ceremony, or keeps the nesting and drops the reasoning. Emitted yaml unchanged at 75839 bytes: this fix is runtime-side and the upload path was already correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
…nd prove it
review 66195, and this is the worst defect in the PR because of WHAT was broken: the two
probes that are THE control for the stale-receipt hazard -- the control the refusal
explicitly required -- were permanently green by construction.
CAUSE, MINE: a blind string-replace. The text " r2_mint_receipt_dir_root," appears in
both the import block and the probe bodies, so adding the import inserted
r2_mint_receipt_dir as a spurious second argument to concat. The composed string was a
path the producer can never emit, so !string_contains(stale, this_attempt_dir) was true no
matter what r2_mint_receipt_path_in did. Flattening the receipt back to a shared path
would have left both probes green -- DESIGN §4b's decoration case verbatim: "permanently
green by construction, carrying no information, and worse than absent because it will be
cited as coverage". And this PR's own annotation cites that separation as protecting the
receipt an operator reads to decide which live token to revoke.
FIXED to the call the sibling probe already made: the expected directory now comes from
r2_mint_receipt_dir(scope: ...), the producer's own construction, so the probe cannot
drift from what the mint emits.
PROVEN BY EXECUTION, BOTH ARMS, because compiling is exactly what failed to catch this.
Planted a falsifier collapsing the scoped arm to the shared root and ran the composition
against the real producer:
falsifier: stale=/tmp/r2-mint-receipts/r2-b-object-write-mint-receipt.txt
attempt2_dir=/tmp/r2-mint-receipts/ separated=FALSE (probe reds)
restored: stale=/tmp/r2-mint-receipts/R-1/r2-b-object-write-mint-receipt.txt
attempt2_dir=/tmp/r2-mint-receipts/R-2/ separated=TRUE (probe greens)
The scratch entry used for that measurement is deleted; it was an instrument, not a
landing artifact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
Sixth re-integration, fifth append-vs-append on this one const. Main accumulated three commits in the file — #11356 emptying the roster, #11256, and #11214 landing fifteen rows for the operator-supplied-token relocation — producing seven conflict regions. Both row sets survive: fifteen plus six, verified by spelling rather than by count. Main's own "THE ROSTER IS EMPTY AGAIN, which is its resting state" paragraph is dropped, and not on this branch's preference: it became false ON MAIN when #11214 landed fifteen rows above it and nothing updated the prose. Keeping it would have carried a claim contradicted by the array directly beneath it, which is the stale-citation shape this file names as the defect to avoid and which this branch has now repaired five times. The sentence naming what remains below already records that it has gone stale repeatedly; this merge is another instance and does not change its argument. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
… wall #11214 landed, so the operator-supplied-token relocation is at base and its fifteen admission rows report CONSUMED — 0 unadjudicated, 0 stale, 15 consumed due for deletion, with this branch's own six adjudicating cleanly. The wall charges a consumed row's deletion to the roster's next touch and this branch is that touch again. Applied under the standing ruling rather than re-asked; this is the third time it has fired on this branch and the second time for rows another lane authored. #11259 is the authored deletion draft for these fifteen and is not emptied by this — it keeps whatever else it carries, as #11274 and #11356 did. Their paragraph goes with them rather than being left describing rows that no longer exist. Verified by identity: no #11214 reference survives anywhere in the file; this branch's six stand unchanged at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
The floor refused this branch with: namespace-wave-admission (0 unadjudicated delta(s), 0 stale admission(s), 15 consumed admission(s) due for correction or deletion). All fifteen are main's own gunbc#11214 rows -- the operator-supplied -token realizer move -- and they are consumed because #11214 LANDED today, so base and head of every build now both carry the relocation and there is no delta left for them to admit. Their deletion is owed on "landing or the roster's next touch", and this change is that touch. The block describing them says so itself: "DISSOLVE-ON: gunbc#11214 merging ... Remove them by that trigger, not by reinterpreting it." The trigger fired; this is the removal it names, not a reinterpretation. The describing block goes with the rows, which is the same shape gunbc#11356 used when it retired the #11193 row and its block together. My earlier audit checked OUR rows against both parents and found one consumed (the #11193 anchor leaf). It did not check MAIN's rows against a main that had moved under us -- #11214 landed after this branch last merged. That is the gap this commit closes: a roster's next touch owes the whole roster's consumed debt, not just the toucher's own rows. Row count: 89 -> 74. cargo check --release -p v1-compiler is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PS4GbDpDLPSCd16NRH5aZ8
…11345) * Cut the namespace transition-admission roster to directory membership. Delete the hand-Rust const so permission is one .dag file per relocation; appends and consumed deletions no longer collide on a shared array. Co-authored-by: Cursor <cursoragent@cursor.com> * Type expected_candidates as DeclarationRef, not a string list. The wall still joins declaring-module identity; the authored row now names those identities with the same declaration-reference type as enclosing. Co-authored-by: Cursor <cursoragent@cursor.com> * Supersede the const-ness-as-safety claim in the roster carrier. Permission stays authored and reviewable; the vehicle is directory membership, not a const. Missing directory is the empty roster and admits fewer rows, never more. Co-authored-by: Cursor <cursoragent@cursor.com> * Home the empty-roster standing record on the membership module. The const's prose about what remains used to rot beside the array. Empty-is-not-permissive and the #11306/#11316 inheritance live on gunbc.namespace.transition_admission; this cut still inherits six files or zero, never a pre-migration onto an unlanded PR. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop the leftover const census and refuse a phantom candidate leaf. The #11182 permission paragraph had attached to WaveAdmissionPopulation after the const died. Candidate DeclarationRef.decl_name must equal Binding.spelling. Co-authored-by: Cursor <cursoragent@cursor.com> * Enumerate the directory-row parse helpers on the seed census. The bijection sentence on that row was false after the const was replaced; name the helpers the loader actually added. Co-authored-by: Cursor <cursoragent@cursor.com> * Do not re-home the consumed gunbc#11193 admission as a directory row. Co-authored-by: Cursor <cursoragent@cursor.com> #11356 owns that deletion (also carried on #11214). Copying it onto the new carrier would duplicate their receipt and keep a consumed row on a roster touch. * Own the used_row fixture strings; Binding is no longer &'static. Co-authored-by: Cursor <cursoragent@cursor.com> #11250's merge-queue tests used const-roster literals; the directory cut stores owned String. * Stop claiming the admission roster is a .rs file. Row files are .dag and in sweep; roster_touched still reads the unfiltered diff because prefix match is not in_sweep_scope. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ve_admission.rs) main's #11214 added fifteen rows to the const NAMESPACE_TRANSITION_ADMISSIONS; this branch deletes that const in favour of authored .dag rows. The rows were not migrated: #11214 has landed, so they are consumed by their own landing (base and head both carry the relocation) and are retired here rather than re-authored. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Apo3eJwXeD9NxuinRp9zMN
The hoist rebinds one spelling in registry image-config; the consumed #11214 token-realizer rows come due on this roster touch and are deleted with it. Co-authored-by: Cursor <cursoragent@cursor.com>
The hoist rebinds one spelling in registry image-config; the consumed #11214 token-realizer rows come due on this roster touch and are deleted with it. Co-authored-by: Cursor <cursoragent@cursor.com>
The hoist rebinds one spelling in registry image-config; the consumed #11214 token-realizer rows come due on this roster touch and are deleted with it. Co-authored-by: Cursor <cursoragent@cursor.com>
What this does
Makes the R2 object-write mint executable from a dispatched workflow job under the run's own federated GCP identity, so the operator's part is (a) creating one empty secret and converging the IAM cells from their own gcloud session and (b) clicking Run workflow. The authoritative roster of those cells is
gunbc.cloudflare.r2_mint_secret_access, not this body — see the Update section at the end for the current ceremony.A — the token source is selected, not spelled
gunbc.auth.access_token_sourcehas carried three realizers of the access ensure for some time, and no entry point in the tree could reach more than one of them, because every entry chose its arm by construction:read_supplied_access_token()thenOperatorSuppliedToken, spelled at eight call sites. An arm chosen at the call site is a binding, not a selection — so a workflow run and a workstation run of the same entry were two different programs, and only one of them existed. That is why the only executed precedent (#10947, the read mint) was an operator pasting a 1-hourya29token into a session.OperatorSuppliedTokenarm (read_supplied_access_tokenand friends) moves to the module that owns the arm. It was authored ingunbc.auth.gcp_secret_access, whose own annotation said it "realizes the OperatorSuppliedToken arm, whichgunbc.auth.access_token_sourcealready carries" — a realizer one module away from the coproduct it inhabits, which is what made the selection unauthorable. Importers are rewired; no name changes.select_access_token_source()— one fold.WIF_ACCESS_TOKENpresent ⇒WorkloadIdentityToken; otherwiseGUNBC_GCP_ACCESS_TOKEN_FILE⇒OperatorSuppliedToken; otherwise a refusal naming both. The workload arm wins on presence, not success: if the variable is present and empty the run refuses through that arm rather than reaching for an operator file. A fallback there would be the absorbing fallback §5 names — a broken federation silently answered by whatever else happened to be on the runner.GcloudPrintTokenis deliberately unreachable from the fold: it is an interactive authentication, and an unattended run must not block on a prompt it cannot answer.gunbc.cloudflare.r2_token_mint_runnow resolves one identity per run and uses it for the bootstrap read and its own Secret Manager writes.gunbc.auth.secret_ref_credentialgainsfetch_secret_ref_credential_with_token;fetch_secret_ref_credentialis now a wrapper over it. This exists to avoid a meaning fork (§3): the mint needs the token after the read, and the previous shape would have forced a workload-federated token through an arm spelledOperatorSuppliedToken— one spelling carrying two materially different identities, with the receipts naming the wrong one.B — two dispatch-only modes on the workflow that already exists
The brief asked for one
workflow_dispatchjob. This adds two modes tofleet-convergeinstead, and that is a deliberate, better-on-the-merits departure:fleet-convergeis already dispatch-only, already pinned to a self-hosted arm64 runner, already carries thegoogle-github-actions/auth@v2step that federates tofleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.comwithid-token: write, and already downloads CI-verified release bins rather than building gunbc on the runner — strictly better evidence than a build. A new job would be a standing claim on a paid runner plus a new emission to enrol; a mode is neither. No hand-written YAML:.github/workflows/fleet-converge.ymlis regenerated fromgunbc.fleet_converge_workflow, which is already in the generated-artifact roster, so drift is already gated.The operator signed off on a dispatch-only addition for this purpose on 2026-09-12.
r2_mint_preflight→gunbc.cloudflare.r2_permission_group_observe observe_account_permission_groups_wetr2_object_write_mint→gunbc.cloudflare.r2_token_mint_run run_object_writeE — reachability is observed, and that is why the preflight is separate
AccountTokens.Createis non-idempotent and returns its value exactly once. Observing runner reachability by attempting the mint would mean learning that the runner cannot reach Secret Manager only after a live credential exists on the account with nobody holding it. The preflight reads the pinned bootstrap version fromsecretmanager.googleapis.comand lists the account's permission groups atapi.cloudflare.com— both hosts the mint depends on, no mutation — so it can be dispatched freely and repeatedly.It is also the vehicle for the discriminating red: dispatched before the grant it refuses typed and located (
THE BOOTSTRAP TOKEN COULD NOT BE READ: ...) with nothing spent.(The earlier
curl 35TLS failure was from a session container to*.r2.cloudflarestorage.comand concernsfetch_absent, a different host and a different operation. Not conflated here.)While making the preflight an entry,
observe_account_permission_groupsgained a typed standing (PermissionGroupListing). An earlier draft decided success by testing the returnedStringfor a"NO "/"THE "prefix — a postcondition keyed to a phrase, green the moment someone improves the wording. The refusal is an arm now; theStringentry is kept as its rendering.C — the grants, as cells
New
gunbc.cloudflare.r2_mint_secret_access, followinggunbc.spark.secret_access_ensure: each cell is derived from the effect that needs it, not from a role someone thought would be enough.cloudflare-account-api-token-bootstraproles/secretmanager.secretAccessorr2_mint_bootstrapreads the pinned v1cloudflare-r2-origin-write-tokenroles/secretmanager.secretVersionAdderadd_initial_versionwrites the minted valuecloudflare-r2-origin-write-tokenroles/secretmanager.secretAccessoradd_initial_versionreads that exact version back to compare bytes — the only part of the write Secret Manager guarantees, so it is a requirement of the write, not a convenienceOperator runs, from their own gcloud session (these authenticate as the human, per
no_principal_can_both_grant_and_read— the control plane that may callsetIamPolicyis deliberately not the workload):Which identity, and why not a dedicated one.
gunbc.auth.github_gcp_federationsays a federating domain supplies its own SA, which argues for a least-privilege SA of the mint's own. This reusesfleet-cloud-convergence@anyway, for a structural reason rather than convenience:gunbc.auth.gcp_secret_accessresolves the granted member fromfleet_cloud_principal_standing()and refuses when it cannot name its subject, so the member of every cell this ensure can write is the convergence principal. A dedicated SA is not a different row, it is a different ensure —memberwould have to become a parameter, and a second federated principal would need provisioning and pool-binding by an operator ceremony strictly larger than the one this lane exists to shrink. The least privilege available today is therefore per-secret: one secret and one role per cell, nothing project-wide. Next rung is the dedicated SA, triggered by grant member becoming a parameter.No user-managed service-account key, no token in GitHub secrets, no token as a workflow input. The only credential either step carries is the short-lived access token the WIF step mints for that run, read in-process rather than interpolated into a command line (the argv exposure
gunbc.credential_argv_exposuremeasures).OPEN — the container create is not expressible as a cell, and it is a decision, not an oversight
store_minted_tokencallscreate_secret_for_attempt→gcp.SecretManager.CreateSecretbeforeadd_initial_version.secretmanager.secrets.createis a project-level permission;SecretAccessGrantis one secret + one role + one condition, and a per-secret cell cannot be written for a secret that does not exist. The read mint never hit this because the operator's own identity carried project admin.Both resolutions have a real cost and the choice is not mine:
cloudflare-r2-origin-write-token, andcreate_secret_for_attemptis fixed to treatALREADY_EXISTSas converged. Keeps least privilege. Needs the actuator change first: today an existing container makesCreateSecretanswer 409 →SecretCreationRefusedafterAccountTokens.Createhas already minted a token whose value is returned once — a token-loss hazard, not merely a failed run.roles/secretmanager.adminonfleet-cloud-convergence@. No code change, but gives the workflow identity create/delete over every secret ingunbai-secrets.Escalated to
royal-eagle-761with A recommended. Do not dispatchr2_object_write_mintuntil this is settled —r2_mint_preflightis safe to dispatch now and is the evidence that matters first.Evidence
gunbc compile --target dag, per entry): the mint, the grant module,ci_spec,fleet_converge_workflow,secret_access_ensure,org_actions_converge, both printer runs, the fabric-m0 probe,secret_access_admission,r2_permission_group_observe..github/workflows/fleet-converge.ymlregenerated from its authority (generated_artifact_gate main_wet_one); the diff is +40/−1, exactly the two modes and their four steps.r2_mint_preflightbefore the grants must refuseTHE BOOTSTRAP TOKEN COULD NOT BE READ: ...with noAccountTokens.Createissued.THE ORIGIN ALREADY PINS THIS CREDENTIAL, whichmint_origin_token_and_storealready refuses on.The mint was not executed from this session.
Landing
Under the public-
dag/landing freeze untilfierce-lark-661's release notice. Pushed for green; not asking for a merge.🤖 Generated with Claude Code
https://claude.ai/code/session_015R9M9cY4iaqsqeaSDpT7g1
Update (2026-09-13) — the OPEN item is resolved, and it turned into a failure-mode row
Operator ruling: option A, with a sharpening that moves where the fix goes. Option B (project-level
roles/secretmanager.admin) is refused on the estate's own design —gunbc.auth.gcp_secret_accessexists precisely so no workflow identity holds project-wide create/delete.The sharpening matters: under the federated identity the actuator must not call
CreateSecretat all. Mapping a 409 after the Create to "container present" would have left the irreversible effect ahead of the check, which is the bug, not a fix for it.What changed
R2MintPreconditionsgainsR2MintCustodyContainerAbsent { intervention }, decided by a readonlyGetSecretof the custody container inside the same ladder as every other precondition — so it is decided beforecloudflare.AccountTokens.Create, not after it.classify_r2_mintstays pure and takes the observed container standing as a parameter.store_minted_tokenis AddVersion-only. TheCreateSecretcall is gone.gunbc.secret_provision_actuatorgainsObservedSecretContainer(asole_constructorseal minted only where the GET's returned resource named the requested project and secret — the same whole-resource comparison, since a suffix match once accepted another project's identically-named secret) andversion_add_capability_for_observed_container, which derives the existingCreatedSecretForAttemptonce a real snapshot exists. The observation seal deliberately carries no snapshot: the container is confirmed long before any credential exists, so a producer shaped likecreate_secret_for_attemptcould only be called by inventing one — and an invented snapshot inside a seal is worse than no seal.completion_evidence: DischargedAt(observe_r2_mint_custody_container)— unlike its two neighbours inr2_origin, this ceremony has a real discharge site: the GET that raises it is also the check that answers it.secretmanager.secrets.get, whichsecretAccessordoes not carry (accessor is about a version's payload).roles/secretmanager.vieweron the write secret is now its own cell, and the witness assertsget.role != back.roleso collapsing the two goes red.Reordering, stated plainly: the access token is now resolved before the precondition ladder, because the container precondition is an observation and needs the run's identity to be decidable at all. The cost is that a run with no credential reports that first rather than the account/custody gap; the benefit is that the one check whose absence could burn a non-idempotent Create is inside the ladder.
The operator ceremony is now four steps, and the order matters (a cell cannot be bound to a secret that is not there):
cloudflare-account-api-token-bootstrapsecretAccessorr2_mint_bootstrapreads the pinned v1cloudflare-r2-origin-write-tokenviewerGetSecret, before the Createcloudflare-r2-origin-write-tokensecretVersionAdderadd_initial_versionwrites the minted valuecloudflare-r2-origin-write-tokensecretAccessorFiled as a class, per the ruling — not left in this PR body:
gunbc.recurring_failure_mode.a_precondition_check_sits_downstream_of_the_irreversible_effect. It is the dual of gunbc#10739's "fabrication downstream of the check" (there the check is correctly positioned and something false flows past it; here nothing is fabricated, the refusal is honest, and it arrives too late), so it is a sibling row rather than a receipt on that one. The discriminating fact is why it survived its one execution: #10947 ran green because the operator's identity held project-widesecrets.create, and a green execution under the most privileged available identity is the weakest evidence an ordering claim can have — privilege is exactly the condition under which a check's position stops mattering. Ceiling is structurally guaranteed:readonlyon the service declarations andsecret_manager_effect_required_permissionsare both modelled, so "a readonly effect, or one whose permissions are unestablished, reachable after a non-idempotent mutation" is derivable rather than reviewable.The escalation is retracted.
Review 64860 (dashboard-only, REQUEST_CHANGES) — all three addressed
r2_mint_gcp_access_token/permission_group_observe_access_tokenwere the same fold twice. Correct, and it was this PR's own thesis unapplied one level up. The composition now lives once, at the least common ancestor:gunbc.auth.access_token_source resolve_access_token(), returning the existingAccessTokenResolutionrather than a new coproduct, since an unavailable source is a refused ensure from where a caller stands. Both wrapper types are deleted.r2_mint_preflight_receipt_pathre-spelled twice. Correct — the diff argued the point against itself.ci_specandfleet_converge_workflownow import the symbol, matching the existinghost_reset_return_receipt_path/spark_apply_receipt_pathform.dag/test/claim/r2_mint_secret_access_witness_test.dagis enrolled, following the citedspark_secret_access_ensure_witness_testprecedent — it asserts the targets join to ther2_origindeclarations rather than being re-spelled, the role of each cell against the effect that needs it, and that no cell is conditioned. The converge entries themselves stay operator-invoked with no CI route, exactly asspark_secret_access_convergeis: the control plane that may callsetIamPolicyis deliberately not the workload.Twin check against
enforcement_gate_positioned_after_the_effect_it_guards— sibling, kept separateChecked before landing, at
royal-eagle-761's request, against that row's own recognition rule. Verdict: genuinely a sibling, not a twin. The new row now cites it in its distinguishing facts so a reader can tell them apart, and the landed row is left untouched.r2_token_mint_rundid refuse honestly whenCreateSecretfailed — it refused about a token that had already been minted.The test that makes this load-bearing rather than taxonomic is the landed row's own next-rung trigger. That trigger is an effect-ordering reader refusing any path that reaches a declared irreversible operation without first branching on the guard naming it. Run it against this defect and it passes clean: no guard named
cloudflare.AccountTokens.Create, and theCreateSecretrefusal that did exist namedCreateSecret. A capability that fully closes that class leaves this one untouched — so merging the rows would retire this one behind a wall that cannot see it.The axis the two share is already named in the landed row's boundary against
declared_refusal_unreachable_from_its_own_constructor("there the arm is never reached; here it is reached, and reached too late"). This is the third point on it: the arm is reached, on time relative to its own operation, and that operation is itself the thing sequenced too late.Review 64955 (dashboard-only, REQUEST_CHANGES) — fixed
The finding is right, and it landed in the worst possible file to get wrong:
r2_mint_secret_accesssaid "two secrets, three cells" and "the three converges" above four grant functions and four converge entries. The count went stale the moment I added theviewercell — the cell the same file calls the one an author would most easily omit.The fix is not
3→4. §4c says an annotation "must not restate what the declaration structurally says," and a count is exactly that restatement — it is the part that rots, and it rots worst in the one module whose purpose is to replace an operator's memory of which bindings are required. So the counts are removed, not corrected: the prose now says "one secret and one role per cell" and "every converge entry in this module, one per cell," and points at the declarations and attest.claim.r2_mint_secret_access_witness_test, which asserts each cell against the effect that needs it. The annotation keeps a short receipt of the stale-count incident as rationale, since that is the irreducible part §4c does admit.Two stale counts in this PR body are corrected the same way. The operator-facing ceremony is the four-step block in the Update section above, and the module is authoritative over both.
— sent from smart-otter-889
Landing sequence, admission standing, and the follow-up
The
src/v1edit is admitted as a consumed ledger entry, not an escape hatch.gunbc.v1_maintenance_standingclasses a diff over admission rosters asMaintenanceRefusal::NewEscapeHatchOrAdmissionRow, and that class dominates purpose admission — so this PR's fifteen rows were refused at disposition until the standing was asked to rule on the class itself. It has: aNAMESPACE_TRANSITION_ADMISSIONSrow is a consumed ledger entry, provided (1) its deletion follow-up is authored before the carrying PR is enqueued and (2) it lands so the rows are gone from main. The distinguishing property is transience with a consumption trigger — a row adjudicates one measured delta by exact identity and is consumed by the merge; it does not exempt. Adjudicated byfierce-lark-661; the receipt row lands ingunbc.v1_maintenance_standingviaeager-raven-113's tree, and this body cites that adjudication until the row is on main.Condition (1) is satisfied: #11259 (draft) deletes all fifteen rows and is open now, before enqueue. It must land immediately after this PR.
PURPOSE admission for the
src/v1edit (gunbc.v1_maintenance_standingv1_seed_standing): the onlysrc/v1change here is fifteen rows in the namespace-wave admission roster — the gate that admits.dagnamespace motion into the required lane — so it serves the v2 self-host program by letting a substrate relocation land, and adds no capability to v1 for its own sake.Why the relocation was not simply dropped. Reaching the
WorkloadIdentityTokenarm without moving symbols is feasible — the selection fold can live ingcp_secret_accesswith zero rows — and it was explicitly rejected: dodging the roster to avoid writing rows is DESIGN §5's workaround, the author-side absorbing fallback that routes around the substrate rather than root-causing it. The layer defect it would have preserved (a run's credential selection owned by the grant-ensure module) is the thing this PR exists to remove.Ordering against gunbc#11240. That PR deleted the same two consumed
gunbc#11156rows this branch had also deleted. It landed first (6e70be2514), and this branch has since merged main: this PR's copy of that deletion and its dissolution receipt are dropped — one receipt per consumption event, and #11240 carries it — leaving only the fifteen relocation rows and their ownDISSOLVE-ON.Consumed-row credit. This branch retires the
gunbc#11193admission row it inherited from #11347. gunbc#11356 is the canonical owner of that deletion — this is the same deletion, taken here because the namespace-wave roster charges a consumed row to whoever touches it next, and identical deletions merge cleanly when #11356 lands.