Skip to content

The R2 account and bucket are declarations, and the mint refuses before it creates - #10925

Merged
briansrls merged 6 commits into
mainfrom
session/stern-moth-79
Sep 10, 2026
Merged

briansrls merged 6 commits into
mainfrom
session/stern-moth-79

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session stern-moth-79.
Pushing to session/stern-moth-79 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

…g entry that refuses before it writes

The Cloudflare R2 credential cluster was fully modeled and cited and nothing
consumed it: account_id and bucket_name were parameters supplied by nobody, and
five FrontierRows said so in identical words. This supplies them from
declarations and performs the one thing that turns the cluster into a
capability -- an entry that runs, and refuses.

gunbc.cloudflare.r2_origin declares what was missing. The account is a STANDING
rather than a literal, because Cloudflare issues the id and this repository
cannot derive it; the fleet's is allocated, and the unallocated arm stays as the
state every future account starts in. The bucket is an ALLOCATION ROSTER, not a
template: gunbc.hostname_allocation already rules that deriving a name by
parsing the purpose is the section 3 nicknaming violation, so the mapping is
explicit, injective on both axes, and the lookup fails closed to Absent rather
than reconstructing a name. Bootstrap custody is a standing too, and that is
where the fail-closed ordering comes from -- nothing can pin an exact version
for a secret that has never been provisioned, so absence is an arm decided
before any network call rather than a fetch that returns nothing.

gunbc.cloudflare.r2_token_mint_run is the executing consumer. It decides every
precondition, fetches, creates, then stores -- in that order, because
AccountTokens.Create is non-idempotent and returns its value once, so a Create
issued before custody is reachable mints a credential nobody can then hold. With
no bootstrap token the entry refuses, typed and located, with nothing spent:

  NO BOOTSTRAP TOKEN IN CUSTODY, NOTHING CREATED: ...

That is the discriminating red, executed on the real path, with an accepted
positive control that reaches R2MintReady from a pinned fixture and joins the
admitted request against the declared account, bucket and permission group.

Three supporting changes. cloudflare.AccountTokens.Create declares
outcome: RestOutcome, mirroring gcp.SecretManager.CreateSecret, and the mint
consumes gunbc.secret_provision_actuator's mutation_status_is_commit_ambiguous
rather than minting a second commit-ambiguity vocabulary -- a 5xx that may have
minted a token must not be classified as safely retryable.
snapshot_minted_secret is a second producer of the sealed OwnedCredentialSnapshot
for a credential that was never on disk, with the same guarantees as
snapshot_credential and the same declared residual (no in-memory SHA-256).
And the bootstrap token shape's lifetime was NoExpiryUntilRevoked, which is
false for the token this fleet holds; it now carries the real expiry. Nothing
refuses on it, because this tree has no Timestamp ordering at all -- that
capability is the named successor.

extdeps.backblaze.b2 lands as its own module under section 3's external
decomposition rule, with no provider hub over it and R2. Its published rates
stay unavailable in extdeps.pricing.object_storage, because nothing observed
here changes that and a remembered number would be worse than an honest gap.

FRONTIER ROWS: two dissolved on their own stated triggers --
vendor_cloudflare_frontier_rows (a gunbc declaration names the account and cites
the vendor) and cloudflare_r2_token_human_burden's row (the refusal arms render
the FleetOnce interventions rather than restating them). The three
Create-triggered rows STAND: no Create has been performed, and retiring them
here would satisfy a trigger while the capability stays dead.

TWO GAPS FILED, NOT FIXED. Only READ is modeled and a durable origin must PUT;
Cloudflare's permission reference publishes no ids (retrieved 2026-09-10), so
the write group id is absent rather than guessed and is resolved by observation
through the already-modeled ListPermissionGroupsRaw, whose path was checked
against Cloudflare's own document. The operator's existing token carries
permission group 5bc3f8b21c554832afc660159ab75fa4, which is not the pinned read
id and is identified by no published document -- it is deliberately written down
nowhere in the tree until that listing is performed. Separately, that token
grants at ACCOUNT scope while this module builds BUCKET-scoped resources; the
difference is recorded, not smoothed over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 10, 2026 03:00
@gunbai-bot gunbai-bot Bot changed the title R2 Account The R2 account and bucket are declarations, and the mint refuses before it creates Sep 10, 2026
…uplicate, and record the permission-group observation

REVIEW FINDING, ACCEPTED AND FIXED AT THE ROOT (review 63054). The bucket
allocation roster this PR added was a line-for-line clone of
gunbc.hostname_allocation's: same scan record, same three-arm verdict, same
absorbing-carry arms, same fail-closed lookup. DESIGN §2 states the test it
failed -- net concepts must not grow by re-invention -- and §6 names the shape
as the forked-logic trap.

BUT DEDUPLICATION WOULD HAVE MISSED THE POINT. hostname_allocation's own
injectivity disposition reported its rung as MECHANICALLY PREVENTABLE and named
its next-rung trigger in these words: a duplicate-refusing keyed roster carrier
whose CONSTRUCTION cannot admit a repeated key -- deferred there only because
std.occurrence_binding_candidates keys on one axis and that roster needs two. A
shared VALIDATOR both modules called would have deduplicated the code and
climbed nothing, leaving that stall exactly where it was.

So std.allocation_roster is a construction, not a check. The carrier is
sole_constructor and its only producers are the empty roster and an `allocate`
that returns a refusal rather than a roster when either axis is claimed, so no
accepted program holds a duplicate on either axis. The lookup consults nothing
before answering -- there is no inconsistent roster for it to guard against, so
the "serve an arbitrary winner" failure has no state in which to occur. Both
modules delete their copies and consume it; per §4b's dissolution rule the
lower-rung PRODUCTION machinery is gone while every duplicate-refusal RED stays
enrolled, now asserting the shared arms.

HONEST RUNG: structurally guaranteed at the carrier, not impossible. The
authored list is still a literal a person can write two colliding rows into,
and the fold refuses it at ONE ingress instead of every lookup guarding against
it. This module's own next-rung trigger is stated once: a declaration form that
builds a keyed carrier without an intermediate list. The earlier caveat that
BucketNameClaimedTwice was unreachable dissolves -- the carrier is generic over
the row, so the second axis is now exercised.

THE PERMISSION-GROUP OBSERVATION, PERFORMED THROUGH THE MODEL. The bootstrap
token is in custody at version 1, so fleet_cloudflare_bootstrap_custody is now
BootstrapCustodyPinned to that exact version -- never "latest", which is a
moving target the fetch cannot verify it was answered about, and which would
make a receipt meaningless. The declared-path red moves one link down the chain
to the GCP token, and the custody-unrecorded arm keeps a discriminating red
from a fixture: §4b's fixture boundary, not a weakening.

BOTH ANSWERS ARE THE GOOD CASE. 5bc3f8b21c554832afc660159ab75fa4 resolves to
Account API Tokens Write -- the grant cloudflare_bootstrap_token_shape declares,
so the token is the bootstrap credential the model expects and not an R2 one;
its account scope is correct for minting. And the write gap closes with an
OBSERVED id rather than a guessed one: 2efd5506f9c8494dacb1fa10a3e7d5b6,
Workers R2 Storage Bucket Item Write, sharing the read group's bucket scope so
a minted origin credential is confined to one named bucket. Recorded in the
OBSERVING layer with its retrieval time, per §3, and joined to the pinned read
id by a witness so the two cannot drift apart unnoticed.

NEW FAILURE-MODE ROW, filed by the lane that found it.
wire_projection_names_a_field_the_envelope_lacks: ListPermissionGroupsRaw
declares `body: String from "body"` against an envelope that carries
{ result, success, errors, messages }. A live authenticated GET returned 200 and
the operation produced NULL -- a successful response yielding a fabricated
absence, which is below the ladder rather than on it, and which no consumer can
see because RestOutcome describes the HTTP exchange and the projection has no
outcome channel. The discriminating pair is in the row: same endpoint, same
credential, same moment, the typed projection answering and the raw one
fabricating. The raw operation is deliberately left standing -- repairing one
`from` string would close the specimen and leave the class invisible. Ceiling 4
with no new capability needed: response shapes are already declared, so a `from`
naming a field outside them is decidable at compile time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
@gunbai-bot

gunbai-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Addressed review 63054 in ae9e5e8. The finding is correct and I fixed it at the root rather than by moving code.

Accepted verbatim: BucketAllocationRosterVerdict / ..Scan / ..step / ..verdict / ..pick / allocated_bucket_name were a structural clone of gunbc.hostname_allocation, down to the transcribed rationale. DESIGN §2 is explicit: net concepts must not grow by re-invention.

But a shared validator would have climbed nothing, so I did not build one. gunbc_hostname_allocation_injectivity_disposition states its own rung as MECHANICALLY PREVENTABLE and names its next-rung trigger in these words: a duplicate-refusing keyed roster carrier whose construction cannot admit a repeated key — deferred there only because std.occurrence_binding_candidates keys on ONE axis and that roster needs TWO. A generic two-axis carrier IS that trigger. Extracting the validator both modules call would have deduplicated the code and left the declared stall exactly where it was.

std.allocation_roster is therefore a construction: AllocationRoster<V> is sole_constructor, its only producers are the empty roster and an allocate that returns AllocationPrimaryKeyRepeated / AllocationSecondaryKeyRepeated rather than a roster when either axis is claimed. No accepted program holds a roster with a duplicate on either axis, so allocation_lookup consults nothing — the "serve an arbitrary winner" failure has no state in which to occur.

Both modules deleted their copies and consume it. Per §4b's dissolution rule the lower-rung PRODUCTION machinery is gone from both while every duplicate-refusal RED stays enrolled, now asserting the shared arms. All 6 hostname witnesses and both r2 roster witnesses are green by execution.

Honest rung, stated in the module rather than claimed here: structurally guaranteed at the carrier, not impossible. The authored list is still a literal someone can write two colliding rows into; the fold refuses it at ONE ingress instead of every lookup guarding against it. Next-rung trigger, stated as the capability: a declaration form that builds a keyed carrier without an intermediate list.

One thing the fix resolved for free: this PR previously had to record that BucketNameClaimedTwice was unreachable while BucketPurpose has a single arm. The shared carrier is generic over the row, so the second axis is now exercised with a fixture and the wall keeps a discriminating red on both axes instead of one that is permanently green by construction.

— sent from stern-moth-79

SECOND REVIEW FINDING (review 63054), and it was right: every declaration in
extdeps.backblaze.b2 sat on the frontier, nothing imported the module, and the
row reasons named no later change -- "lands ahead of the storage-provider
decision it will serve" is an unbound wait on something nobody scheduled, which
is DESIGN §3c's dangling arm rather than its declared-frontier arm. §5 closes
the escape it was leaning on: a dissolution condition describes how admitted
debt ends, it does not authorize creating the debt.

The reviewer's remedy was to drop the module from this PR. I did not, because
the brief that commissioned it asked for B2 in this change specifically and not
as a second PR. What I did instead is remove the condition that made it red:
the module now has a consumer in this same closure.

extdeps.pricing.object_storage carried two backblaze.com locators as bare
literals -- b2_storage_rate_unavailable and b2_egress_beyond_free_allowance --
which is a Backblaze fact spelled inside a comparison table, the §3 layer
inversion in miniature. Those locators move to the module that owns them and
the catalog imports them. So B2 is consumed by execution here, through every
fold that reads the object-storage catalog, and one duplicated spelling of a
vendor URL disappears rather than a new one being added.

WHAT DID NOT MOVE, AND WHY THAT MATTERS: the rates. b2_storage_rate_unavailable
stays unavailable with its measured cause intact -- the headline per-TB number
is client-rendered and absent from the fetched markup, with no machine-readable
price list published. Nothing observed while writing this module changes that,
and replacing an honest unavailability with a remembered number would be a
fabricated price underneath the storage decision this all exists to inform.
Locators are citations; rates are the thing being cited.

The credential shapes remain a frontier and now say so honestly: their consumer
is the storage-provider comparison that picks between R2 and B2 and authorizes
against the winner, and their trigger is an executing b2_authorize_account.
That is the declared-frontier arm with the trigger stated beside it, which §3c
admits, rather than the dangling arm it was in before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
@gunbai-bot

gunbai-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Second finding from review 63054 addressed in 0deb176.

The finding is correct. Every declaration in extdeps.backblaze.b2 was on the frontier, nothing imported the module, and the row reasons named no later change — "lands ahead of the storage-provider decision it will serve" is an unbound wait on something nobody scheduled, which is §3c's dangling arm, not its declared-frontier arm. §5 closes the escape it was leaning on: a dissolution condition describes how admitted debt ends, it does not authorize creating it.

I did not take the suggested remedy (drop it from this PR), and here is why, stated so it can be overruled. The brief that commissioned this change asked for the bare-minimum B2 module in this PR specifically and not as a second one. So rather than defer it, I removed the condition that made it red: it now has a consumer in this closure.

extdeps.pricing.object_storage was carrying two backblaze.com locators as bare literals (b2_storage_rate_unavailable, b2_egress_beyond_free_allowance) — a Backblaze fact spelled inside a comparison table, which is the §3 layer inversion in miniature. Those locators moved to the module that owns them and the catalog now imports them. B2 is consumed by execution here, through every fold that reads the object-storage catalog, and a duplicated spelling of a vendor URL disappears rather than a new concept being added.

The rates deliberately did not move. b2_storage_rate_unavailable stays unavailable with its measured cause intact — the headline per-TB number is client-rendered and absent from the fetched markup, no machine-readable price list. Nothing I observed changes that, and swapping an honest unavailability for a remembered number would put a fabricated price underneath the storage decision this work exists to inform. Locators are citations; rates are the thing cited.

The credential shapes stay a frontier and now say so accurately: consumer is the storage-provider comparison that picks between R2 and B2 and authorizes against the winner; trigger is an executing b2_authorize_account. That is the declared-frontier arm with the trigger stated beside it, which §3c admits.

If the view is that "consumed" via citation locators is too thin a thread to carry a whole upstream module into this PR, say so and I will pull B2 out and land it with the comparison that reads it — but that is a decision against the brief, not a defect in the diff, so I am flagging it rather than taking it unilaterally.

Also on this head: the roster consolidation from the first finding (ae9e5e8), plus the permission-group observation this PR was blocked on. Both R2 answers came back the good case — 5bc3f8b2… is Account API Tokens Write (the bootstrap grant the mint model declares, not an R2 grant), and the write gap closes with an observed id, 2efd5506f9c8494dacb1fa10a3e7d5b6 (Workers R2 Storage Bucket Item Write, bucket-scoped like the read group). Recorded in the observing layer with retrieval time per §3 and joined to the pinned read id by a witness.

— sent from stern-moth-79

Brian Searls and others added 2 commits September 10, 2026 03:50
… located it

THE CREATE WAS ATTEMPTED AND NOT SPENT. With the account id declared, the
bootstrap token in custody at version 1, and a live GCP access token in hand,
this entry ran against the real account. It read the bootstrap credential out of
Secret Manager -- a real GET, in the request log -- and then aborted BEFORE any
Cloudflare request was transmitted:

  TypeError: fixture cannot record unreplayable value kind "Map"
             -- refusing to write an unfaithful fixture

Every effect call routes through record/replay, and v1.stage0.recorded_fixture
treats Map, Set, Closure and Fn as unreplayable. AccountTokenPolicy.resources is
a Map<String, String> because that is what Cloudflare accepts: a JSON object
whose KEYS are the IAM resource names. So AccountTokens.Create cannot execute at
all, and neither can any other operation carrying a Map input.

THE DISCRIMINATOR IS IN THE SAME SESSION. cloudflare.AccountTokens
ListPermissionGroups executed live against the same account minutes earlier,
same credential, same endpoint family, and returned its rows; its inputs are a
Secret and a String. The only difference between the executable read and the
inexecutable mutation is one Map-valued input -- not authority, not transport,
not the endpoint.

NOTHING IRREVERSIBLE HAPPENED AND NOTHING IS AMBIGUOUS. No request transmitted,
no response, no token on the account, no readback owed, no retry question. The
abort is also not a typed refusal from this model -- the arms below never ran --
and recording it as one would be exactly the inflation §4b names.

THE ROUTE AROUND IT IS NOT TAKEN. Respelling resources as a list of key/value
pairs makes the call work today and models the wire LESS faithfully: the
upstream shape is an object with dynamic keys, and a list of pairs admits
duplicates and an order the wire does not have. That is reshaping the model to
fit a limitation of the tooling, and §5 names noticing it as the line-stop.

NEXT-RUNG TRIGGER, as the capability: a fixture encoding that can faithfully
record and replay a Map-valued effect input, order-independent -- at which point
this entry reaches the Create with no change to the model above it. The three
Create-triggered frontier rows therefore still stand, now for a located reason
rather than a missing credential.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
…pretending to be consumed

TWO FINDINGS FROM review 63068, both correct.

THE RUNG CARRIER CONTRADICTED THE CLIMB. gunbc_hostname_allocation_injectivity_
disposition still reported MECHANICALLY PREVENTABLE, still cited
hostname_allocation_roster_verdict as the thing every production lookup consults,
and still named the duplicate-refusing construction as an UNADOPTED next-rung
trigger -- while the annotation eight lines below asserted that trigger had
fired and the cited symbol no longer existed anywhere in the tree. §4b(1): the
reported rung must equal the rung established by executed evidence. §3: cite the
symbol, not the position, and a stale name is decidable. The prose and the
modeled carrier disagreed, and the carrier is the one a reader consults.

It is rewritten in the same change that climbs, which is the whole point --
a climb that leaves its own rung claim behind is how an inflated rung survives.
It now reports STRUCTURALLY GUARANTEED and explicitly declines the top rung: the
authored list is still a literal a person can write two colliding rows into, so
structural impossibility remains the ceiling with the trigger owned once by
std.allocation_roster. The executed evidence is named rather than asserted --
the duplicate-refusal witnesses that stayed enrolled through the migration.

THE OBSERVATION ROWS WERE CONSUMED ONLY BY TESTS THAT RESTATED THEM. Two of the
three witnesses were self-joins: r2_observed_bootstrap_group_is_account_api_
tokens_write looked up an id literal and asserted a name literal authored eight
lines apart in the same file. That is §5's rule against an oracle copied from the
tree it checks -- automating its update collapses it to measure() == measure().
Both are DELETED rather than weakened.

What survives is the one genuine cross-authority join: the observation against
extdeps.cloudflare.r2's PINNED read id, authored by a different module for a
different reason, which fails if either side drifts.

The rest are now a declared frontier with named consumers and triggers, not
silence: the write id dissolves when extdeps.cloudflare.r2 declares a bucket-item
WRITE grant from it and an entry mints a token carrying it; the bootstrap id
dissolves when an executing entry reads the token's OWN permission groups and
refuses on disagreement with the declared shape -- a token-verify operation this
repository does not model yet, which is the honest reason it is not consumed
rather than an unbound wait. Enrolled in the census.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
@gunbai-bot

gunbai-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Both findings from review 63068 addressed in 9440e02. Both were correct.

1. Stale rung claim + citation of a deleted symbol. Exactly right, and it is the sharper of the two: I climbed the rung and left the typed carrier of the rung claim asserting the state it used to describe. gunbc_hostname_allocation_injectivity_disposition still said MECHANICALLY PREVENTABLE, still cited hostname_allocation_roster_verdict as the thing every production lookup consults, and still named the duplicate-refusing construction as an unadopted next-rung trigger — while the annotation below it claimed that trigger had fired and the cited symbol no longer existed in the tree. §4b(1) and §3 both bite, and the disposition is the one a fold consults.

Rewritten in the same change that climbs, which is the point — a climb that leaves its own rung claim behind is how an inflated rung survives. It now reports STRUCTURALLY GUARANTEED and explicitly declines the top rung: the authored list is still a literal someone can write two colliding rows into, so structural impossibility stays the ceiling with the trigger owned once by std.allocation_roster. Executed evidence is named rather than asserted — the duplicate-refusal witnesses that stayed enrolled through the migration.

2. Observation rows consumed only by witnesses that restate them. Also right, and the self-join characterisation is precise: r2_observed_bootstrap_group_is_account_api_tokens_write looked up an id literal and asserted a name literal authored eight lines apart in the same file. §5 rules that out directly — an oracle copied from the tree it checks, where automating the update collapses the assertion to measure() == measure(). Both self-joins are deleted rather than weakened.

What survives is the one genuine cross-authority join: the observation against extdeps.cloudflare.r2's pinned read id, authored by a different module for a different reason, which fails if either side drifts.

The remaining rows are now a declared frontier with named consumers and triggers rather than silence — the write id dissolves when extdeps.cloudflare.r2 declares a bucket-item WRITE grant from it and an entry mints a token carrying it; the bootstrap id dissolves when an executing entry reads the token's own permission groups and refuses on disagreement with the declared shape. That last one names why it is genuinely not consumable today: this repository models no token-verify read. Enrolled in the census.

Context the reviewer could not have from the diff: the Create this PR builds toward was attempted live and did not execute — it aborted before any Cloudflare request with fixture cannot record unreplayable value kind "Map", because AccountTokenPolicy.resources is a Map<String,String> and the effect recorder refuses Map inputs. Nothing was spent, no credential exists. gunbc#10929 fixes the encoding and is unmerged; I am not building on it. The three Create-triggered frontier rows therefore still stand, now for a located reason rather than a missing credential.

— sent from stern-moth-79

One conflicted path, dag/gunbc/census_closure_frontier.dag, and it is the
ordinary both-sides-appended case on a roster several lanes write to: main added
colo_types_frontier_rows while this branch added backblaze_b2_frontier_rows and
cloudflare_permission_group_observation_frontier_rows. All three are kept.

RESOLVED BY READING BOTH SIDES, NOT BY TAKING EITHER. --ours or --theirs would
have silently dropped the other lane's rows, and a frontier row that vanishes in
a merge is a tracked debt that stops being tracked with nothing left to notice --
the worst thing to lose here, because the roster IS the mechanism by which an
obligation stays visible.

CHECKED AFTER RESOLVING, because a clean text merge is not a clean result on a
roster:
  - the three Create-triggered rows still stand (admit_r2_object_read_api_mint,
    admit_account_token_api_mint, r2_s3_access_pair_from_created_token). They
    must, since no Create has executed, and a careless resolution is exactly how
    they would have disappeared.
  - no obligation appears twice under a different spelling: a tree-wide scan of
    frontier_row_decl subjects at (module_path, decl_name) grain reports no
    duplicates, so the text merge did not keep two copies of one row.
  - all three imports survive and the census witness is green by execution --
    cloudflare_frontier_rows_are_in_the_census returns true on the resolved tree,
    which is what proves every row is still reachable from the census rather
    than merely present in a file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVRvDcj3MXa2Y5iPMDwNH
@gunbai-bot

gunbai-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Answering the CI-failure notice on f70f450f4: the failing lane is red on main itself, and pushing a fix from this branch is not the remedy.

The discriminator settles it without reasoning about source — main ran the same SHA twice with opposite verdicts:

run SHA time result
34436354976 3fc663339 04:13Z success
34436627031 3fc663339 04:17Z failure

Same commit, same tree, four minutes apart. Every main run since has failed, and main's tip 0d6b6653e reports the identical two blockers this PR does:

adjudication BLOCKING phase=parse identity=<phase> cause=parse (17 error(s))
adjudication BLOCKING phase=namespace-wave-admission identity=<phase> cause=namespace-wave-admission (no head index)
adjudication REFUSED standing=measurement_completed blockers=2

A parse phase reporting 17 errors on a tree that parsed clean four minutes earlier is not a property of any diff.

Measured on this branch rather than assumed: required-witnesses-build passes on this head; only the floor lane is red. A whole-tree compile of the branch (dag + src/v2) names zero parse errors in any file this PR touches — the blocking errors it does report are pre-existing type mismatches in other lanes' files (llm_attempt_receipt_witness_test, source_integration_landing_spine_witness_test, lens/reference_deps).

I have not pushed a speculative fix, deliberately. Guessing at a break I did not cause and cannot reproduce in my tree would muddy the record of what this PR changed, and both blockers point at runner state rather than source — no head index is a missing artifact, not a defect in a declaration. I have not confirmed that cause; I am naming where I would look. Touching the floor instrument or the runner is not my lane, so it is escalated rather than acted on.

Everything else on this head stands: conflict resolved by merge commit keeping both frontier populations (census witness green by execution), one APPROVE with no actionable findings, and the earlier REQUEST_CHANGES stale with both findings fixed in 9440e02.

— sent from stern-moth-79

@gunbai-bot

gunbai-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Correction to my previous comment. The same-SHA success/failure pair I cited was from the fleet-desired workflow, not witnesses — I ran gh run list --branch main without filtering by workflow and quoted one instrument as evidence about a different one. In witnesses, no SHA has been run twice, so the "fixed tree, changing answer" argument has no instance in the lane that is actually red. My "runner state, not source" conclusion was wrong and would have sent a break-hunt at the runner instead of the corpus.

With the right lane, the range is one commit. witnesses on main is green through 4c629f5b2 (03:34) and red from 523657c15 (03:42), and nothing else sits between them:

523657c15 = #10630 "Boot Mt. Collins diskless through modeled operations" (12 files, ~2022 lines, 10 new .dag modules).

The count matches exactly. In dag/gunbc/machine_intake/megarac_media_attach.dag, added by that PR:

lines 212-218    7 errors
lines 303-306    4 errors
lines 441-446    6 errors
                17 total
error: source annotation sits inside a declaration body.
       Only module-item grain is modeled; move it above the declaration it describes.

17 — which is cause=parse (17 error(s)) to the digit.

So it is a corpus break, and no head index is best read as a consequence rather than a second cause: if parse refuses, there is no head index to build. DESIGN §4c admits only standalone leading comment blocks attached to module-scope declarations, so the remedy is to hoist those three blocks above the declarations they describe. (I hit this exact class myself earlier tonight.)

I have not touched it — that is #10630's lane, and a drive-by edit to another module while the tree is red is how two lanes start fixing the same thing. One further finding in the same file set, not part of the 17 and not a parse error, so whoever takes the fix should expect it behind rather than treat it as their own regression: boot_image_export_ownership.dag:121 — non-exhaustive match: missing variant FleetAccountNotificationService.

What stands from my previous comment, unchanged and independently measured: required-witnesses-build passes on this head, and a whole-tree compile of this branch names zero parse errors in any file this PR touches.

— sent from stern-moth-79

@briansrls
briansrls merged commit 23598ca into main Sep 10, 2026
2 of 4 checks passed
@briansrls
briansrls deleted the session/stern-moth-79 branch September 10, 2026 13:48
@briansrls
briansrls restored the session/stern-moth-79 branch September 10, 2026 13:51
gunbai-bot Bot pushed a commit that referenced this pull request Sep 10, 2026
…sclassified it

The wave wall refuses this PR's own repair. It classifies

  base {} -> head {extdeps.transports.rest}

as NewPoolCoincidenceResolution, which refuses. By the 2026-08-27
operator ruling in gunbc.compiler_frontend_program_interlock, an author
writing the import that resolves a name the module was ALREADY SPELLING
is AuthoredReferenceResolution, which auto-admits -- a distinction that
ruling drew after the wall refused gunbc#9485, also a one-line import
repair. This is that case exactly.

It lands in the wrong arm because locally_authored_claim_added decides
authorship with names_leaf, which reads `c.members` and never `c.target`.
The base names the leaf from the wrong module and the head names it from
the right one, so `names_leaf(head) && !names_leaf(base)` is false. The
predicate can see a name appearing where it was ABSENT; it cannot see a
name whose SOURCE changed.

What makes that a defect rather than a rough edge: a REDUNDANT blanket
import would have tripped the blanket_targets branch and auto-admitted.
The wall is easier to satisfy by writing worse code, so it teaches the
wrong repair. I did not write the blanket import -- noticing you are
implementing a workaround is the line-stop signal (DESIGN section 5) --
and escalated instead. Admit was ruled; the predicate fix lands
separately against a green main, because repairing a wall in the same
motion that asks it for an exception makes the exception look bought.

The row also fixes the attribution, which I had wrong twice: neither
contributing change is wrong in isolation. #10925 wrote the import when
the fn WAS declared in secret_provision_actuator; #10923 then deleted it,
rehoming it correctly and leaving one edge at the old address. The defect
exists only in their composition.

Roster was empty at this base -- verified against 495cde7 rather than
against my other worktree, which carried two rows deleted upstream. The
row carries its own deletion trigger.

cargo check -p v1-compiler: Finished, 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jFtgPtXxTj1kE8wwZUNsG
briansrls pushed a commit that referenced this pull request Sep 10, 2026
…le that declares it (#10945)

* Import mutation_status_is_commit_ambiguous from the module that declares it

main is red on the declarations phase:

  declarations FAIL IMPORT-MEMBER-ABSENT dag/gunbc/cloudflare/r2_token_mint_run.dag:69:3
  imports `mutation_status_is_commit_ambiguous` from `gunbc.secret_provision_actuator`,
  which declares no such name

The symbol has exactly one declaration in the corpus, `extdeps.transports.rest` at
rest.dag:111, and `secret_provision_actuator.dag:238`'s own annotation already names
that as its home. The importing module ALREADY imports `extdeps.transports.rest` at
line 8, so this moves one name between two import lists that both exist. Nothing is
renamed, added or deleted.

Measured on both arms through `gunbc compile --entry
dag/gunbc/cloudflare/r2_token_mint_run.dag`, discriminating rather than asserted:

  unfixed (origin/main)   1 blocking error(s), 815 advisory
  fixed                   0 blocking error(s), 815 advisory, 132 files emitted

The advisory count is identical across the arms, so the edit moved exactly the one
thing it claims to. Note the compile exits 0 while carrying a blocking error, so the
count line is the signal and the exit code is not.

Cut as its own change rather than folded into the census PR that surfaced it: one PR,
one question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jFtgPtXxTj1kE8wwZUNsG

* Admit the stranded-caller repair's binding delta, and say the wall misclassified it

The wave wall refuses this PR's own repair. It classifies

  base {} -> head {extdeps.transports.rest}

as NewPoolCoincidenceResolution, which refuses. By the 2026-08-27
operator ruling in gunbc.compiler_frontend_program_interlock, an author
writing the import that resolves a name the module was ALREADY SPELLING
is AuthoredReferenceResolution, which auto-admits -- a distinction that
ruling drew after the wall refused gunbc#9485, also a one-line import
repair. This is that case exactly.

It lands in the wrong arm because locally_authored_claim_added decides
authorship with names_leaf, which reads `c.members` and never `c.target`.
The base names the leaf from the wrong module and the head names it from
the right one, so `names_leaf(head) && !names_leaf(base)` is false. The
predicate can see a name appearing where it was ABSENT; it cannot see a
name whose SOURCE changed.

What makes that a defect rather than a rough edge: a REDUNDANT blanket
import would have tripped the blanket_targets branch and auto-admitted.
The wall is easier to satisfy by writing worse code, so it teaches the
wrong repair. I did not write the blanket import -- noticing you are
implementing a workaround is the line-stop signal (DESIGN section 5) --
and escalated instead. Admit was ruled; the predicate fix lands
separately against a green main, because repairing a wall in the same
motion that asks it for an exception makes the exception look bought.

The row also fixes the attribution, which I had wrong twice: neither
contributing change is wrong in isolation. #10925 wrote the import when
the fn WAS declared in secret_provision_actuator; #10923 then deleted it,
rehoming it correctly and leaving one edge at the old address. The defect
exists only in their composition.

Roster was empty at this base -- verified against 495cde7 rather than
against my other worktree, which carried two rows deleted upstream. The
row carries its own deletion trigger.

cargo check -p v1-compiler: Finished, 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jFtgPtXxTj1kE8wwZUNsG

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 10, 2026
…h sides had live rows

The same file conflicted as last merge, but not the same case, and the difference decides the
resolution. Last time this branch had 40 SCM rows and `main` had reached an EMPTY roster, so
keeping ours dropped nothing. THIS TIME BOTH SIDES CARRY LIVE ROWS: ours are the 40 SCM deltas,
main's is one for the gunbc#10945 stranded-caller repair. Choosing either side whole would delete
obligations the other still owes, and a delta nobody adjudicated is exactly what this wall exists
to refuse -- so the rosters are APPENDED, 41 rows.

Main's account of the stranding is kept verbatim rather than summarised, because it is the history
of a defect rather than this branch's commentary, and because it independently reached the
attribution this session had already corrected to: neither contributing change was wrong alone.
#10925 wrote the import while the fn was still declared in `secret_provision_actuator`; #10923 then
deleted it, rehoming it to `extdeps.transports.rest`; the defect lived only in their composition.
Main's note adds the detail this session did not have -- #10923's floor concluded four hours before
#10925 landed, so the verdict that would have caught the stranding was computed against a base that
did not yet contain the importer it was about to strand. A concluded verdict, correct about the
world it measured, in a world that no longer existed at merge time.

TWO DEFECTS OF MINE ON THE WAY IN, both caught by a check rather than by my reading. The union was
written by a script that took `&[` to mean the array literal and matched the TYPE ANNOTATION
`&[TransitionAdmission]`, emitting a malformed row header -- found by reading the resulting array
instead of trusting the script's success line, which is the false-deletion shape this branch hit
earlier. Then the appended row carried the wrong indentation for its nesting, and the pre-commit
hook refused the commit; that is the hook doing its job, not an obstacle to route around.
`cargo check -p v1-compiler --lib` ran on the working tree (the runner reports applying the patch,
so it checked this edit rather than a committed SHA) and finished clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
briansrls pushed a commit that referenced this pull request Sep 10, 2026
…in_offers_single_cabinet (#10968)

main is RED on the declarations check:

  required-ci: declarations FAIL IMPORT-MEMBER-ABSENT
    dag/test/claim/colo_nj_central_south_census_test.dag:13:51
    `test.claim.colo_nj_central_south_census` imports `grain_admits_single_cabinet`
    from `test.claim.colo_cabinet_density_witness`, which declares no such name

NEITHER CONTRIBUTING CHANGE IS WRONG IN ISOLATION, and the composition is the defect.
#10865 (a675b70, merged 19:45:49Z) renamed `grain_admits_single_cabinet` to
`grain_offers_single_cabinet` in the shared witness module and updated every call site it
could see. #10864 (de7622e, merged 19:46:19Z) imports the old name, which existed when
that PR was written. The two merged THIRTY SECONDS APART, both MERGEABLE/CLEAN with all four
required checks green, and neither floor verdict could contain the other's change.

This is the third instance today of one class: a stranded caller, produced by two changes
whose verdicts were each concluded, correct, and computed against a base that excluded the
other. The earlier two were the megarac §4c break (#10630) and the
`mutation_status_is_commit_ambiguous` rehoming (#10925 wrote the edge, #10923 deleted its
target). A 30-second gap is the sharpest form: waiting longer for a floor to conclude does
not help when both floors HAD concluded.

The repair follows the rename rather than reverting it. `grain_offers_single_cabinet` carries
the identical signature `(g: RetailGrainStanding) -> Bool` and the identical body — it folds
`retail_grain_single_cabinet_wording` to a Bool — so this is a pure spelling change at eight
call sites, and the new name is the one the shared module now declares.

EVIDENCE
- GREEN by execution: `gunbc compile --entry dag/test/claim/colo_nj_central_south_census_test.dag`
  -> rc=0, `0 blocking error(s)`, `compiled: 61 files emitted`, zero IMPORT-MEMBER-ABSENT.
  The completion marker is quoted beside the count deliberately: a count with no completion
  marker beside it is a claim about the pipeline rather than about the subject.
- RED control, on the real acceptance path: main's own required floor on de7622e, run
  34522373789, reporting this exact finding. The failing content is the parent of this commit.

Both contributing lanes are archived, so this was cut by a blocked lane rather than routed.


Claude-Session: https://claude.ai/code/session_01998xs4ojJKWGptxcuxVWHN

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 11, 2026
…ures; the private gap as a typed stall; the within-tree half re-homed

Answers review 63772 / 63786 (a transcribed measurement with no producer) and the
manager's 2026-09-11 rulings, by landing the producer rather than softening the
claim.

THE INSTRUMENT. v1_compiler.bin.joint_claim_join computes, per change, the
export-surface entries removed (declared | variants | reexported, base minus
head) and the import claims added and retired, from only the diff-touched files
on both sides through namespace_wave_admission base_records and diff_sides, and
joins every admitted pair. A claim is joined only while LIVE when the entry
leaves: not retired by the removing change itself, and in retrospective mode not
retired by any change that landed between the pair. raw_candidates is reported
beside findings so the width the exclusion removes stays a number. Seven
fixture-boundary controls, real parser, real join: one positive control per
instance shape (variant deleted, name renamed, re-export dropped), one RED per
exclusion, one for the pair predicate, one for a claim already at the base.

WHAT IT ANSWERS, cited by invocation in the plan and not transcribed:
`joint_claim_join 6a54695^..f078c59` (first-parent main since
2026-08-28, default 3-day window) reports subjects=373 raw_candidates=236
findings=4 -- exactly the three 2026-09-10 instances (instance 1 is two names).
The two candidates the first-exclusion-only run reported as findings were both a
third commit retiring the claim between the pair (#10617 for the harness one; the
DensityMarketedMax one likewise); two artefacts with one cause was a defect in the
retrospective's pair predicate, built in as the second exclusion with its own
control rather than described.

TWO CORRECTIONS THE INSTRUMENT MADE TO THE SCOPE. Instance 2 (#10923 x #10925)
was a MOVE, not a dropped re-export: the base declared
mutation_status_is_commit_ambiguous in secret_provision_actuator and #10923
re-homed it; the instrument reports `(declared)` and the plan's table now says so.
The dropped re-export stays covered by the surface and keeps its control.

THE RULINGS. Checkpoint 2 stops at the pull-requests:read permission row on a
required job (the operator's trade); the dashboard reader is costed in section
4.1 and preferred on every axis but authority. The private gap is a typed
GuaranteeStall row (joint_claim_join_private_corpus_unindexed_stall) with the
overlay's extra-source-root trigger as its grounding, not prose. The within-tree
field-set case is NOT a checkpoint here: its home is
witness_that_fails_to_compile_is_absent_rather_than_red, and this change appends
a receipt there widening the trigger from claim-root files to the ruled capability
-- every construction site of a type whose field set changed, regardless of gate
closure -- per section 4b(3), rather than minting a second authority.

Hand Rust is enumerated in gunbc.joint_claim_join_seed_growth (33 items, no impl
block, every one citable) and rostered in seed_growth_admission.

Verified remotely: clippy -D warnings clean on the bin, 7/7 tests, the
retrospective above, and v1_src_dag_parse over the corpus with every new .dag
present: 5461 files parse-clean, no findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzQcQRu3WKxJuWV2zaiHSW
briansrls pushed a commit that referenced this pull request Sep 11, 2026
…t 1: joint_claim_join, the instrument behind its figures (#11044)

* Scope the jointly-incompatible-open-PR mitigation: a claim-channel x surface join over the declaration index, advisory-first

Three 2026-09-10 reds on main were pairs of individually-green, textually
disjoint PRs whose union does not compile. This records the scope of a
mitigation and builds nothing, answering the four questions the brief asked
before any shape:

THE CUT. All three public instances are one finding kind, ImportMemberAbsent,
and only one was a deletion: #10865 was a rename, and #10923 dropped a
RE-EXPORT with no declaration deleted anywhere. The cut is therefore the
surface predicate the declarations rider already applies, import_surface_has
(declared | variants | reexported) as a base-minus-head DELTA, not the kind of
edit. The general case is every claim channel the index carries -- imports,
citations, rostered rows (#10769, #10718 are the same shape through the other
two) -- and the residuals the index cannot see are named: field rename,
arity/signature, and pure freshness (private #49 x #50).

THE COST. namespace_wave_admission already reconstructs a base index beside
the head index from only the diff-touched files, per run, in the witnesses
lane; the join is A(I) & R(D) minus the claims D itself retires, a set
intersection, not a pairwise compile. Measured retrospectively over 374
first-parent commits since 2026-08-28: the raw join is a 207-hit candidate
list (the superset trap); with the one exclusion it is 5 hits, 4 of them the
three brief instances, the fifth an ordering artefact an open-PR-set join
excludes by construction. Recall 3/3 on the window's ImportMemberAbsent reds.

THE PLACEMENT. No new job (roster closed): a rider on the parse phase, like
rostered_row_join, advisory, reporting and annotating without pushing a phase
failure; the one emitted-workflow change is a pull-requests: read permission
row. The freshness caveat of a push-time verdict is stated as the reason this
sits beneath the ceiling.

THE REFUSAL. One finding per removed entry x claiming site: the (module,
name) and which surface it left, the sibling PR and head, the claiming module,
in_declaration and SourceLocation; NotEvaluated when the open-PR set is
unobservable, never an empty hit set.

gunbc-private: no parse phase, the overlay is outside DAG_PARSE_SWEEP_ROOTS,
so the join is public-only today; 113/114 private modules import from public,
a cross-repo exposure no public PR is ever compiled against, named and left
to warm-badger-62's lane.

Ceiling stays with gunbc.plans.ci_merge_freshness and the landed, deferred
receipt_is_admissible; this record retires with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzQcQRu3WKxJuWV2zaiHSW

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-rung-drops.md

* Checkpoint 1: joint_claim_join, the instrument behind the scope's figures; the private gap as a typed stall; the within-tree half re-homed

Answers review 63772 / 63786 (a transcribed measurement with no producer) and the
manager's 2026-09-11 rulings, by landing the producer rather than softening the
claim.

THE INSTRUMENT. v1_compiler.bin.joint_claim_join computes, per change, the
export-surface entries removed (declared | variants | reexported, base minus
head) and the import claims added and retired, from only the diff-touched files
on both sides through namespace_wave_admission base_records and diff_sides, and
joins every admitted pair. A claim is joined only while LIVE when the entry
leaves: not retired by the removing change itself, and in retrospective mode not
retired by any change that landed between the pair. raw_candidates is reported
beside findings so the width the exclusion removes stays a number. Seven
fixture-boundary controls, real parser, real join: one positive control per
instance shape (variant deleted, name renamed, re-export dropped), one RED per
exclusion, one for the pair predicate, one for a claim already at the base.

WHAT IT ANSWERS, cited by invocation in the plan and not transcribed:
`joint_claim_join 6a54695^..f078c59` (first-parent main since
2026-08-28, default 3-day window) reports subjects=373 raw_candidates=236
findings=4 -- exactly the three 2026-09-10 instances (instance 1 is two names).
The two candidates the first-exclusion-only run reported as findings were both a
third commit retiring the claim between the pair (#10617 for the harness one; the
DensityMarketedMax one likewise); two artefacts with one cause was a defect in the
retrospective's pair predicate, built in as the second exclusion with its own
control rather than described.

TWO CORRECTIONS THE INSTRUMENT MADE TO THE SCOPE. Instance 2 (#10923 x #10925)
was a MOVE, not a dropped re-export: the base declared
mutation_status_is_commit_ambiguous in secret_provision_actuator and #10923
re-homed it; the instrument reports `(declared)` and the plan's table now says so.
The dropped re-export stays covered by the surface and keeps its control.

THE RULINGS. Checkpoint 2 stops at the pull-requests:read permission row on a
required job (the operator's trade); the dashboard reader is costed in section
4.1 and preferred on every axis but authority. The private gap is a typed
GuaranteeStall row (joint_claim_join_private_corpus_unindexed_stall) with the
overlay's extra-source-root trigger as its grounding, not prose. The within-tree
field-set case is NOT a checkpoint here: its home is
witness_that_fails_to_compile_is_absent_rather_than_red, and this change appends
a receipt there widening the trigger from claim-root files to the ruled capability
-- every construction site of a type whose field set changed, regardless of gate
closure -- per section 4b(3), rather than minting a second authority.

Hand Rust is enumerated in gunbc.joint_claim_join_seed_growth (33 items, no impl
block, every one citable) and rostered in seed_growth_admission.

Verified remotely: clippy -D warnings clean on the bin, 7/7 tests, the
retrospective above, and v1_src_dag_parse over the corpus with every new .dag
present: 5461 files parse-clean, no findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzQcQRu3WKxJuWV2zaiHSW

* State the population compile's boundary on the row: compilability of the claim-root population, not evaluation of it

A file that compiles and whose test fns are declared but never reached from the
entry passes that wall untouched (cool-badger-34's four fns, 2026-09-11). That
hole is discriminating_arm_built_but_never_enrolled's, named as the neighbour so
the word POPULATION is not later cited for a scope the trigger never claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzQcQRu3WKxJuWV2zaiHSW

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 11, 2026
…t main's new witnesses hit

Two blockers on 761f9af, and only one of them was the roster.

1. THE RECEIPT. Required floor run 34651932681 reported all three gunbc#11071 rows as CONSUMED
ADMISSION ... already satisfied at the base, and refused with 3 consumed admission(s) due for
deletion on this roster-touching change. Deleted. Third time this branch has carried another lane's
fresh rows through a merge and had the wall order their deletion, and the union was right all three
times: what deletes a row is the receipt, never the expectation, however reliable the expectation
has become. Main's TRIGGER paragraph stays -- it records when they came due.

2. A SEMANTIC MERGE CONFLICT I CAUSED. The floor also refused with

  fabric_m0_commit_witness_test.dag:194: type mismatch:
    expected 'Coproduct(CasStoreFailure)', got 'Coproduct(CasUnreadableSlot)'

This branch changed `CasStoreRefused` to carry a `CasStoreFailure` rather than a `CasUnreadableSlot`
directly, because a conditional write can reach the store, satisfy its precondition, and still fail
to PUBLISH -- reporting that as a malformed head was the conflation the widening removed. Main then
landed fabric-M0 witnesses written against the old shape. NEITHER SIDE IS WRONG ALONE; the defect
exists only in their composition, which is exactly the stranded-caller shape that took main down
earlier this week with #10923 and #10925. The difference is that this time the type change is mine,
so the repair is mine: I am the one composing them.

THE FLOOR NAMED TWO SITES AND THERE WERE FOUR, across three files -- it stopped early. Repairing
only the cited lines would have left two more to surface on the next run, which is the same
fix-the-quoted-instance-rather-than-retire-the-claim pattern this branch recorded two commits ago
when a narrowed word survived forty lines below its own correction. All four now wrap as
`CasSlotObservationRefused { cause: ... }`, and the constructor is imported where it was missing.
Verified by sweeping for the old shape corpus-wide rather than by re-reading the error: 0 remain.

Roster census after deletion: 40 rows, 31 SCM_MERGE_BASE_COHOME + 9 SCM_SOURCE_RECOVERY_REHOME.
All three fabric modules resolve clean; cargo check -p v1-compiler --lib finished clean; section 4c:
0 violations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant