Skip to content

scm: a typed read-command result that names no destination - #9443

Merged
briansrls merged 5 commits into
mainfrom
session/scm-read-command
Aug 28, 2026
Merged

briansrls merged 5 commits into
mainfrom
session/scm-read-command

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Recut onto current main at 616cd8f9d83. The diff is now exactly four files and carries no parent commits:

dag/gunbc/scm/read_command.dag
dag/gunbc/scm/repository_load.dag
dag/test/claim/scm_read_command_witness_test.dag
dag/test/claim/scm_repository_load_witness_test.dag

Why it was recut

The previous composition reached main through the save branch's ancestry, so it carried repository_save.dag and its witness. Neither is on main; both belong to #9434, which is draft. Merging this PR would therefore have landed the parked save half as a consequence of branch topology rather than as a decision anyone made — nobody doing anything wrong, the ancestry simply outranking the park.

The park is respected rather than routed around. There is no convert_to_draft event on #9434 and draft is not this tooling's default (#9433 was never toggled and merged), so the hold is unexplained rather than accidental, and the correct response to an unexplained hold is to leave it standing.

The load refinement stays, and is not scope creep. Splitting RepositoryLoadRefusal out of RepositoryLoad is what lets ScmReadRepositoryUnavailable carry a refusal that cannot hold a success. Without it this module's unavailable arm would be constructible holding RepositoryLoaded, with nothing to refuse the pair. It is the enabling half of this change. The dependency was checked before cutting and runs one way: the save witness imports RepositoryLoadRefused; nothing read-side references save.

The verdict is the shape, not a field

ScmReadResult<T> = ScmReadAnswered { path: String, answer: T }
                 | ScmReadRepositoryUnavailable { cause: RepositoryLoadRefusal }

There is no answered: Bool anywhere. A stored verdict would be a second representation of what the arms already determine, so a value could be constructed whose flag says answered and whose payload is a refusal, with nothing to refuse it. Here that pair has no spelling.

The type argument carries the verb's answer — CommitLog for scm_read_log, RepositoryStatus for scm_read_status — so the three ways a load can refuse are composed once rather than restated per verb. What the generic does not buy is recorded in the module itself, measured rather than assumed: a CommitLog in a ScmReadResult<RepositoryStatus> compiles with 0 blocking, because the type argument is substituted into the formal before the inhabitance judgment runs. That is a mitigatable rung with its next-rung trigger named, not a guarantee being claimed.

An empty repository is an answer. CommitLog's NoCommitsYet sits inside ScmReadAnswered — a repository with no commits was read successfully and truthfully has nothing to report. Filing it beside "the document was unparseable" is the not-applicable-rendered-as-malformed conflation: opposite owners, opposite repairs.

Evidence

Mutating scm_read_log to render a load refusal as an empty log answer — the empty-observation narrow — reds three claims while leaving the fourth green:

                empty      absent   verdict   schema
BASELINE          0           0        0         0
MUTATION          0 <--       1        1         1     <-- empty is an EXPECTED GREEN (control)
RESTORED          0           0        0         0

p_empty staying green is the result, not a gap. It is a negative control firing correctly. An empty repository and an unavailable one are different states; the mutation corrupts only the second; so the claim about the first is supposed to survive, and its survival is what establishes that the other three are bound to the distinction rather than merely to the module. A mutation that reds everything only proves the claims are attached to the code, which is what breaking anything gets you.

All six claims in the read-command witness and all five in the load witness are test fn, so the floor's strip_prefix("test fn ") scan discovers them. Both keystones return true on this tree over current main.

One claim whose name came down rather than up

scm_rc_status_refuses_in_the_same_arm_on_the_same_path_as_log previously asserted sameness of cause in its name while its body only checked that neither verb answered — and two verbs that both fail are not evidence they share a composition.

The repair was to weaken the name, not strengthen the body, and the reason is the rule: a stronger claim is reachable exactly when it can be pinned to something this repository controls. The arm and the path are ours. The error string inside RepositoryFileUnreadable is host prose — we neither author it nor pin it — so a claim comparing full causes would be asserting a property of someone else's text. The body compares the arm and the path and binds the error _; the name now says exactly that.

What is deliberately absent

No command enum — choosing a verb from argv is dispatch, dispatch is realization, and a Log|Status coproduct would pull the CLI's concern into the value layer. Two functions instead.

No strings that are phrasing — the only Strings are a filesystem path and a commit message, both facts authored outside this repository. No message: field, no pre-formatted line, no field whose only consumer would be a printer. Wording, layout, ordering, plurals and colour are the binding's business and appear nowhere.

No binding. How this value reaches a human is a separate ruling, still open.

🤖 Generated with Claude Code

@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Closing until verified — the refactor in this branch invalidates two witnesses that I am mid-way through updating, and a draft PR still runs the full floor. Reopening once it compiles green locally-remote, so the fleet spends one run on finished work rather than three on work in progress.

— sent from gentle-eagle-360

@gunbai-bot gunbai-bot Bot closed this Aug 27, 2026
@gunbai-bot gunbai-bot Bot reopened this Aug 27, 2026
@gunbai-bot gunbai-bot Bot changed the title SCM work scm: a typed read-command result that names no destination Aug 27, 2026
@gunbai-bot
gunbai-bot Bot changed the base branch from main to session/scm-repository-save August 27, 2026 13:38
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 27, 2026 13:38
@gunbai-bot
gunbai-bot Bot changed the base branch from session/scm-repository-save to main August 27, 2026 14:28
@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed, and the fix landed on #9434 rather than here — dag/gunbc/scm/repository_save.dag is that PR's file, and this branch carries it only because it is stacked. Commit dee767f9c68, merged forward into this branch.

The finding is correct. RepositorySaved { bytes_written: Int } put the unit in the field name and left the type a bare Int. The raw scalar is legitimate at the extdeps.filesystem boundary under cited-spec fidelity — the host really does report an Int — and propagating it inward was exactly the step this type took.

Took the wrap, not the marker. ByteSize (std.measure, Measure<Memory, One, Nat>) already is the authority for a quantity of bytes, so a tracked feature:/dissolve-on: marker would have registered debt in a place where the construction already existed.

One thing the wrap forced that is worth flagging, because it is a change in shape rather than in type. ByteSize counts a Nat, the host reports an Int, and the only available crossing — std.checked_arithmetic nat_magnitude — takes an absolute value. Applying it unguarded would turn a negative host report into a plausible byte count: fabricated plausible output, in the one module whose stated point is that adjudication precedes the write. So the crossing is guarded and the negative gets its own arm carrying what the host actually said:

type RepositorySave
  = RepositorySaved { path: String, written: ByteSize }
  | RepositorySaveRefusedByCodec { path: String, cause: RepositoryEncodeRefusal }
  | RepositoryFileUnwritable { path: String, error: String }
  | RepositoryWriteByteCountUnrepresentable { path: String, reported: Int }

nat_magnitude now runs only on the non-negative domain, where it is the identity.

RepositoryWriteByteCountUnrepresentable is a boundary obligation (§4b) — external reality observed, typed, admitted or refused — not a class on the ladder, so it carries no next-rung trigger. It also has no witness, and the annotation says so as a limit rather than leaving it to read as a gap: reaching it needs a host write that succeeds and then reports a negative, which no SubstrateInputsOnly fixture can author, and a real write refuses at the hermetic boundary before any count exists. The arm is kept because the alternative is not "no arm" — it is nat_magnitude silently producing a byte count from nonsense. Deleting it would delete the refusal, not the case.

Thanks for the read on the rest; the T inference gap note you singled out has since been sharpened by measurement — three serialized probes established that the judgment is reached and declines because the formal is a type variable, so the trigger now names the capability (the type argument substituted into the formal before the judgment runs) rather than a position class.

— sent from gentle-eagle-360

@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

CI is red and the failure is not from this PR — declining to push a fix, with the evidence.

The failing job is required-witnesses-floor (job 98608330854). Verbatim:

required-ci: parse OK 4200 file(s) parse-clean
required-ci: declarations FAIL IMPORT-MEMBER-ABSENT dag/product/fabric/contention.dag:23:19:
  `product.fabric.contention` imports `grant_duration_seconds` from
  `product.fabric.supply`, which declares no such name

dag/product/fabric/ is untouched by this PR — the diff is dag/gunbc/scm/* and dag/test/claim/scm_*. The cause is on main: #9395 added contention.dag calling grant_duration_seconds, #9397 renamed that accessor in supply.dag to grant_duration_bound_seconds returning Second? and did not update the caller. Each was green against a base lacking the other; they edit different files, so git merged them cleanly and together they break main. No textual merge could have caught it.

Compiling contention.dag as an entry locally gives five blocking diagnostics, and three are the missing name while two are exhaustiveness obligations — occupancy_basis_of is missing UnobservedGrantDuration, and the affordability match is missing QuoteNotPriceableWithoutDuration. So this is not a mechanical rename: the accessor returns Second? because the underlying fact became partial, and the annotation in supply.dag states that QuoteNotPriceableWithoutDuration is not a fifth way of being unaffordable. Picking a default or a wildcard arm to make it compile is the fabrication that rename exists to prevent, in a domain this PR does not own. It is assigned to the owning lane.

Every open PR against current main fails this way, including ones whose branch does not contain the offending commit — the pull_request event evaluates the merge with current main, so branch ancestry exempts nothing. I confirmed that on a sibling PR whose branch predates the break and which fails identically.

No fix will be pushed here. This PR carries an approval with no findings (review 56869); it becomes mergeable when main is repaired, without any change to this diff.

— sent from gentle-eagle-360

@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in f1ff2675123. Review 56995's finding is correct and I've applied it.

One correction to the citation, which does not affect the substance. The cited lines 579,592,607 do not exist — the file is 130 lines. The three claims are at 99, 112, 127. I mention it only so the next reader can find them.

The finding stands exactly as written, and the cause is worth recording because it is not visible in either diff on its own. This branch forked from session/scm-repository-save before that branch's enrollment fix, so it carries a pre-fix copy of the save witness. Left alone, landing both PRs would have reverted the fix — the defect would have been introduced by merge order rather than authored by either change. That is why the review's framing is right: the file's mutation-receipt annotation describes an order property, the floor's strip_prefix("test fn ") scan discovers no identity for any of the three claims, and the annotation ends up citing evidence that never runs.

Applied: fn → test fn for the three claims. The keystone returns true after the change.

One deliberate non-change. scm_sv_unrepresentable_repository at line 90 stays a plain fn. It returns RepositoryEnvelope, not Bool — it is a fixture helper, not a claim, and promoting it would enroll a non-claim into the floor.

On the review's parenthetical about the _test name suffix: the file is already scm_repository_save_witness_test.dag, matching the load witness. I read that clause as describing the claim-name prefix convention (scm_rl_ vs scm_sv_), which is intentionally different because the two files witness different subjects; if a rename was meant, say so and I'll take it.

The two write-arm claims the annotation discusses remain unauthored, unchanged by this commit and for the reason the annotation gives: a write has no hermetic input carve-out, so authoring them would produce a route gap that blocks rather than merely sitting inert. That route is a wet-route enrollment, not another claim in this file.

— sent from gentle-eagle-360

Recut onto current main so the diff matches the scope this PR claims.

WHY THE RECUT. The previous composition reached main through the save
branch's ancestry, so it carried repository_save.dag and its witness --
neither of which is on main, and both of which belong to #9434, which is
draft. Merging this PR would therefore have landed the parked save half
as a consequence of branch topology rather than as a decision anyone
made. Nobody would have done anything wrong; the ancestry would simply
have outranked the park.

The park is respected rather than routed around. No convert_to_draft
event exists on #9434 and draft is not this tooling's default, so the
hold is unexplained rather than accidental -- and the correct response to
an unexplained hold is to leave it standing.

WHAT REMAINS, and why the load refinement is not scope creep: splitting
RepositoryLoadRefusal out of RepositoryLoad is what lets
ScmReadRepositoryUnavailable carry a refusal that CANNOT hold a success.
Without it this module's unavailable arm would be constructible holding
RepositoryLoaded, with nothing to refuse the pair. It is the enabling
half of this change, not a neighbour travelling with it.

The dependency runs one way, checked before cutting: the save witness
imports RepositoryLoadRefused, and nothing read-side references save. So
dropping save costs this PR nothing.

Both keystones return `true` on this tree over current main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot force-pushed the session/scm-read-command branch from f1ff267 to 616cd8f Compare August 27, 2026 23:30
@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Recut at 616cd8f9d83. Prior approvals were given on a different diff and should be re-taken.

The change reviewers approved is intact; what changed is the PR's scope. The earlier composition reached main through the save branch's ancestry and so carried repository_save.dag and scm_repository_save_witness_test.dag. Neither is on main, and both belong to #9434 — which is draft. Merging this PR would have landed the parked save half as a side effect of branch topology rather than as a decision anyone made.

The diff is now four files: read_command.dag, repository_load.dag, and their two witnesses. Save is out.

Two of the review findings against the old body were correct and are fixed in the rewrite:

  1. The body showed the type without its parameter. It is ScmReadResult<T>, and the body now says so, along with what the type argument does not buy — a CommitLog in a ScmReadResult<RepositoryStatus> compiles with 0 blocking, because the argument is substituted into the formal before the inhabitance judgment runs. That is recorded as a mitigatable rung with its trigger named, not papered over.

  2. The body claimed the same-cause claim "compares the causes." It does not, and should not. The claim is scm_rc_status_refuses_in_the_same_arm_on_the_same_path_as_log; it compares the arm and the path and binds the error string _. That string is host prose we neither author nor pin, so a claim comparing full causes would assert a property of someone else's text. The name came down to meet the body rather than the body being pushed up to meet the name — the stronger claim is reachable exactly when it can be pinned to something this repository controls, and here it cannot.

Both keystones return true on the recut tree over current main; all six read-command claims and all five load claims are test fn, so the floor discovers them.

No route-gap rows are pre-enrolled. Whether any identity here gaps is for the floor receipt to say — enrolling a speculative row would correctly fail as stale.

— sent from gentle-eagle-360

documents as twice-removed

`repository_load_refusal_path` had exactly one occurrence in the tree --
its own definition. No consumer, so DESIGN section 6 residue.

WHAT MAKES THIS WORSE THAN ORDINARY DEAD CODE: read_command.dag's own
header, in this same PR, cites this exact helper shape being removed
twice before -- once as `checkout_succeeded`, once from
`gunbc.scm.ancestry` under review 56207 -- and states the reason that
survives. This change re-added the third instance while documenting the
first two. Neither a lens nor a green run can see that; only reading the
two files against each other does.

The surviving rationale in the deleted comment block was about the TYPES
(why no `loaded: Bool` exists), not about the accessor, so it moves to
`type RepositoryLoad` rather than being deleted along with the function.
A prose row removed for one reason must not silently take its contents
with it.

It gains the reason the shape keeps recurring, which was written down
nowhere: with no consumer the accessor is residue, and WITH one it is
worse -- a fourth refusal arm would be absorbed by the projection instead
of failing to compile at each site that must decide about it.

Both keystones return `true` after the deletion.

Reported by review 57012.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 2a0d65a70e0. Review 57012 is correct, and it is a sharper finding than it states.

repository_load_refusal_path had exactly one occurrence in the tree — its own definition. No consumer, so §6 residue, as reported.

The part worth recording: read_command.dag's header in this same PR cites this exact helper shape being removed twice before — once as checkout_succeeded, once from gunbc.scm.ancestry under review 56207 — and gives the reason that survives. So this diff re-added the third instance of a shape it was simultaneously documenting as twice-removed. Neither a lens nor a green run can see that; it is only visible by reading the two files against each other, which is what the review did.

Deleted the helper. The rationale in its comment block was about the types — why no loaded: Bool exists anywhere in the module — not about the accessor, so it moves to type RepositoryLoad rather than being deleted along with the function. A prose row removed for one reason should not silently take its contents with it.

I extended it with the reason this shape keeps recurring, which was written down nowhere and is the stronger half:

with no consumer the accessor is §6 residue, and with one it is worse — a fourth refusal arm would be absorbed by the projection instead of failing to compile at each site that must decide about it.

That is why the removal keeps being the right call rather than a style preference, and it now sits where the next author meets it before adding a fourth instance.

Both keystones return true after the deletion.

— sent from gentle-eagle-360

@gunbai-bot

gunbai-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Verified: this red is main's, not this PR's. The fix is #9535, already open and approved.

Both failures on run 33127582340 are the same two that fail on every PR based on current main:

required-ci: lane=witnesses phases_run=3 failed=2
  FAILED PHASE namespace-wave-admission (0 unadjudicated delta(s), 53 stale admission(s))
  FAILED PHASE floor refused: BarrenTestSidecar count=1
    -- dag/test/claim/resolved_call_emission_identity_witness_test.dag

resolved_call_emission_identity_witness_test.dag landed in #9436 with seven claims all declared plain fn, so the floor enrolls nothing from it and refuses the barren sidecar. I opened #9535 to promote those seven declarations; it is approved and its CI is running. When it lands, this red clears here and for every other open PR at the same time.

I predicted this red before it arrived and checked the log anyway rather than assuming — a predicted failure still has to be the failure you predicted.

One thing I am explicitly not concluding. No scm_ claim appears anywhere in the failure output, and that is not evidence that this PR's claims passed. The floor refused during preparation, so none of them ran. Reading an absent failure as a pass would be execution-provenance loss: a refused-before-execution run and a clean run render identically here, and only one of them is a measurement.

What this PR's claims are backed by: both keystones return true locally over this exact tree, all six read-command claims and all five load claims are test fn so the floor will discover them, and review 57018 approved the recut diff. The floor's own verdict on them is still owed and arrives once #9535 unblocks it.

No fix is pushed here. Pushing one would mean putting an unrelated file into a diff whose scope is the read-command result, and the repair belongs in main.

— sent from gentle-eagle-360

@gunbai-bot

gunbai-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Verified: the build-lane failure is inherited from main, not from this PR. No fix is pushed, and none is available from this branch.

Run 33131098881, build lane — one phase of five fails, and it is regen:

required-regen: first_generation_equal=false planned=138 executed=138 declared_divergent=1 [main.rs]
required-ci: regen FAIL generated surface drift:
  v1_compiler_emit_core_support.rs, v1_compiler_emit_go.rs,
  v1_compiler_emit_python.rs, v1_compiler_emit_rust.rs
required-ci: lane=build phases_run=5 failed=1
FAILED PHASE regen (1 failure(s))

The four drifted files are stage0 emitter mirrors. This PR touches four .dag files under dag/gunbc/scm/ and dag/test/claim/ and no mirror, no emitter, and no Rust. The same failure reproduces on main across consecutive push runs, so it predates and is independent of this branch.

The other four build phases pass on this head, which is the discriminating part — a broken diff would not pass them:

required-ci: emit-compile ... baseline=[Completed status=0] mutation=[Discriminated ...]
required-ci: partition-crates rendered=14 matches=14 drifted=0 absent=0
required-ci: generated-artifact rostered=70 adjudicated=70 matches=70 drifted=0 unadjudicated=0

A correction to something I said on this PR earlier. I previously wrote that this PR was "blocked only by the main floor breakage." That was unsound on its face and I should have caught it: BarrenTestSidecar is a floor refusal and cannot fail a build lane. I read one failing lane and generalised to both. The barren refusal is now fixed on main (#9535), and this build-lane regen failure is a separate, still-open item that the floor fix was never going to clear.

The regen drift is unassigned, older than tonight, and with my parent session. I am deliberately not adopting it: finding the log line does not make it mine, and taking it here would put four emitter mirrors into a diff whose scope is a typed read-command result.

The floor lane on this head is still running. This PR's own evidence is unchanged: APPROVE on the recut (review 57018), no open REQUEST_CHANGES, both keystones return true locally over this exact tree, and all eleven claims are test fn so the floor discovers them.

— sent from gentle-eagle-360

gunbc-ci-auto-heal and others added 2 commits August 28, 2026 02:52
The floor reported route_gap_unenrolled=3 on this branch, all three in
scm_read_command_witness, all with one cause:

  the hermetic route has no arm for Read (operation declares no
  mock_response)

  scm_rc_an_absent_repository_is_unavailable_not_an_empty_answer
  scm_rc_status_refuses_in_the_same_arm_on_the_same_path_as_log
  scm_read_command_keystone_holds

Same boundary already recorded for the load witness: extdeps.filesystem
declares no mock_response, so the hermetic frame has no arm for a FAILING
read. All three claims exercise the absent-repository path, and the
keystone inherits the gap by composing them. They pass under `gunbc run`,
which performs the real read; they cannot reach their subject hermetically.

MEASURED, NOT PREDICTED. These were foreseeable and were deliberately NOT
pre-enrolled: enrolling an identity that does not gap is a stale row and
reds the build, which is what stale_route_gap counts. The rows are added
now because a run reported these exact three identities.

Enrolment records the gap as known debt. It does not make the gap
acceptable and it is not a fix: the remedy is a hermetic arm for a failing
read, which belongs to the filesystem boundary and not to this PR.

Roster 112 -> 115; the module still evaluates and returns its list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit cd8593e into main Aug 28, 2026
1 of 2 checks passed
@briansrls
briansrls deleted the session/scm-read-command branch August 28, 2026 04:07
@briansrls
briansrls restored the session/scm-read-command branch August 28, 2026 04:13
gunbai-bot Bot pushed a commit that referenced this pull request Aug 28, 2026
#9443 replaced `RepositoryLoad`'s flat refusal arms with
`RepositoryLoadRefused { cause: RepositoryLoadRefusal }`. This witness
still matched the flat `RepositoryFileUnreadable` arm, which no longer
exists at that position, so it broke the moment #9443 merged.

This is the ordinary adaptation to a landed refinement, not a defect in
this branch. It was pre-announced before #9443 landed, precisely so a red
arriving on a PR nobody had touched would not be misdiagnosed.

One import and one arm. The claim does not change meaning: it still
asserts that a refused save leaves no file, and it still discriminates on
the same refusal cause -- it now reaches that cause through the outer arm
that owns it, which is what the split exists to enforce.

The PR stays draft and parked; this only keeps it coherent against main.

Keystone returns `true` after the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot mentioned this pull request Aug 28, 2026
6 tasks
@gunbai-bot

gunbai-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Correction to this merged PR's body. Two statements in it are wrong; the landed source is correct in both cases. Recording it here because the body is the review record the next contributor reads, and one of the two would teach them a false mechanism.

1. The body invents a mechanism the module deliberately did not claim

The body says:

a CommitLog in a ScmReadResult<RepositoryStatus> compiles with 0 blocking, because the type argument is substituted into the formal before the inhabitance judgment runs.

That "because" is mine and it is unfounded — and it is very close to the inverse of what was measured. The module's own annotation records three serialized probes, and the middle one refutes the explanation:

kernel at a NON-GENERIC record field   REFUSES
UNDEFINED NAME at the generic field    REFUSES   <-- inference DOES reach this position
kernel at that same generic field      ADMITS

The undefined-name probe exists precisely to prove that inference reaches this exact position rather than skipping it. So the judgment is not bypassed and the position is not skipped; what does not happen is that the concrete type argument is enforced there. The source says only that, and says it carefully — it states the rung is mitigatable, that this is "NOT a claim that the payload type is enforced here," and that the mechanism was "measured rather than reasoned about."

The body then reasoned about it anyway and published a cause. That is this repository's diagnostic name accurate about the situation, silent about the mechanism class: the observation (0 blocking) was real, the explanation was invented, and an invented explanation in a merged review record is what the next contributor builds on.

The annotation in the landed source is the authority here, not this body.

2. "The only strings are a path and a commit message" is false transitively

The body claims:

the only Strings are a filesystem path and a commit message, both facts authored outside this repository

But the refusal arm carries host prose through composition:

ScmReadRepositoryUnavailable { cause: RepositoryLoadRefusal }
  -> RepositoryFileUnreadable { path: String, error: String }   <-- host error text

The intent of that section is sound and the code honours it: no message: field, no pre-formatted line, no field whose only consumer is a printer, and no phrasing authored by this module. That is why the same-cause claim binds error as _ and had its name weakened rather than its body strengthened — the error string is host prose this repository neither authors nor pins. But "the only Strings are" is a statement about the whole value, and the whole value does carry one more, inherited from the load refusal.

The accurate claim is the narrower one the source already implements: no string in this module is presentation prose authored here.

Not affected

The landed code, the eleven claims, and the three route-gap enrollments all stand. These are review-record defects, not code defects, and I am not proposing a follow-up change to the source — the source is what is right in both cases.

Found by the advisory review thread; verified against origin/main before posting rather than taken on report.

— sent from gentle-eagle-360

briansrls added a commit that referenced this pull request Aug 28, 2026
* Six unresolvable names in the v2 root's emitted Rust: qualify the cross-module calls and use the declared list_length (#9547)

The v2 compiler root emits cleanly -- 0 blocking, 2083 advisory, 175 files --
and the emitted crate does not compile. Measured on 00b242b81a1 with
`gunbc compile --entry src/v2/compiler/00_compile.dag --target rust`, then
cargo over the emitted tree with its own emitted Cargo.toml: 20 rustc errors.

Six of them are source defects in this repository's own .dag, not emitter
defects and not self-host work, and this commit is those six.

THREE ARE NAMES USED WITH NEITHER AN IMPORT NOR A QUALIFICATION.
`decl_facts` is declared in v2.std.decl_index and used bare in two modules;
`PartialFunction` is declared in std.algebra and used bare in a type position.
The interpreter resolves them, so nothing refused; the emitter reports them as
`unlisted import use` advisories and emits the bare name, which is E0425. The
repair follows the idiom already on one of the two lines -- grammar_coverage.dag
declares no imports at all and qualifies every other cross-module reference
inline -- so these are qualified rather than imported. inferred_tree.dag already
carries five imports, so PartialFunction is added to that list.

THREE ARE A FREE-FUNCTION SPELLING OF A METHOD. `length(xs:)` has no declaration
anywhere in .dag; `length` is a MethodDeclaration in dag/std/methods.dag that the
interpreter intercepts. The corpus spells this `.length(` at 804 sites and
`list_length(` at 306; only reference_deps used the free form. Repointed at
std.types.list_length, whose declared parameter is `items`, not `xs`.

MEASURED, EACH ROUND A FULL RE-EMIT AND A FULL CARGO BUILD OF THE EMITTED TREE:
20 -> 17 after the three qualifications, 17 -> 14 after the three list_length
sites. Exactly the fixed errors disappeared both times and NOTHING WAS UNMASKED
behind them. That is worth stating because it is the outcome the masking
argument says not to assume: rustc stops after name resolution, so every count
here is a lower bound on a fully-resolving crate, and 20 -> 17 -> 14 establishes
only that no masking occurred AT THIS LAYER, never that none exists.

WHAT IS DELIBERATELY NOT IN THIS COMMIT, because none of it is a source defect:
five host builtins with no .dag body (layer_import_facts and the four
*_resolution_facts), four errors from Filesystem.Read emitting `.await?` against
an unbound handle in a sync fn, three emitter type-argument defects, one
unclassified E0391 variance cycle, and two deliberate compile_error!
sentinels that 05_emit_rust.dag emits instead of fabricating a default.

No Rust touched. No roster edited. No policy changed.

Co-authored-by: Brian Searls <briansearls1@gmail.com>

* The census memo's fill was never attributed, so one claim was charged for two compiles the roster shares (#9560)

* The census memo's fill was never attributed, so one claim was charged for two compiles the roster shares

gunbc#9477 made a shared memoized compile's fill a preparation cost rather than
the first payer's, because a merge-blocking per-claim ceiling charged with an
order-dependent number is a fact about discovery order and not about the tree.
It wired that rule into `compile_dag_rust_emit_check` and not into its census
sibling, which gunbc#9428 had memoized for exactly the same reason. One
accounting rule, two homes, applied in one of them.

MEASURED, not inferred. On main run 33131296988 (b6003a45e) the floor refuses
with `completed_over_cost_requirement=1` and `failed=0`:
`test.claim.callable_candidate_ambiguity_witness.neither_green_source_refuses_
and_neither_mis_resolves` at 5812ms against the 5000ms fail-stop. That run
carries 259 per-claim `[floor-shared-fill]` lines and NOT ONE of them names any
row of this file -- while the row demonstrably paid two shared compiles, being
the first claim to reach both `green_named_authority_source` and
`green_own_declaration_source`, each of which a later claim then reads free.
Zero reported fill beside a charged total that is almost entirely fill is the
discriminating evidence that the charged figure is the TOTAL term, not the
marginal one the limit is specified against. Its two siblings show the same
shape from the other direction: 1652ms and 3130ms, each the first to reach one
further source, and the two claims that read those sources second appear on no
over-cost line at all.

THE FIX IS THE ONE THE RECEIPTS ALREADY RULED FOR. No limit is raised, no row
is grandfathered, no witness is withheld: the missing bracket is added, so a
census MISS records its fill through the same accumulator the sibling memo
writes and `run_claim_measured` performs the same split it already performs.
Nothing is exempted -- the fill is still measured on the enforcing clock, still
counted, and now still REPORTED, as a `[floor-shared-fill]` line these rows
have never emitted. Their absence in the next floor run would mean this change
did not execute; their presence is the arm-ran control.

The two forward-freeze receipts are corrected in the same change. The census
one asserted that the split is "reported, never subtracted from what a claim is
charged", which was true of this memo and is the sentence that describes the
defect; the attribution one said the accumulator is written "only on an
emit-check MISS", which was the whole of it. No declaration is added, so
neither receipt's hand-item delta moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Name the forcing class that decides warm-versus-net, and name the third state as the one that must not exist

The bracket in the previous commit fixes ONE instance. What made that instance
authorable is that the two treatments for a shared artifact are two
hand-written call sites with no carrier relating them, so "claim-forced and
unbracketed" is a writable state that nothing refuses.

THE DISCRIMINATOR IS WHEN THE ARTIFACT CAN BE FORCED.
Preparation-forceable -- every identity it can be asked for is knowable before
the fold -- is warmed ahead and billed to preparation; `both_closure_edge_index`
is this arm, and the run reports `provenance=built-by-preparation` for both
index identities the floor's resolves can reach. It correctly carries no fill
bracket, which matters because absence of a bracket was read as evidence of a
defect during this investigation and was the wrong instrument.
Claim-forced -- what it will be asked for is a property of the claim, so it
cannot be warmed ahead -- must record its fill, because a witness's synthetic
source is not knowable before the fold.

THE THIRD STATE IS THE DEFECT, and it is invisible because the number it
produces is REAL: a true measurement of something, charged to a row that does
not own it. Worse than a wrong number, it can become permanent -- gunbc#9517
would freeze rows above the line under a shrink-only contract, and a row frozen
for cost it does not own can never be made cheap, so it can never leave.

PROSE IS NOT A WALL AND THE ROW SAYS SO. Rung: mitigatable, on review
diligence; the third state stays writable and this paragraph will not stop the
next memo. Next-rung trigger: a memoized host artifact DECLARES its forcing
class and the warm-or-net treatment is DERIVED from it, at which point the
third state has no spelling. That construction is not made here and is not
claimed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* Two 04_infer rows carried counts that had rotted — name the instrument, and stop restating the superseded figures as history (#9462)

* 04_infer: the traversal-idiom count rotted to 16 while the tree carried 29 -- name the instrument

explicit_return_conformance_note argued that collect_explicit_return_values is not a new
shape but the seed's ordinary traversal idiom, and grounded that on a transcribed count:
"16 such sites on origin/main" across seven named modules.

Measured, both on origin/main and on this branch: 29 sites across EIGHT modules.

  04_emit_info 1 · 04_sigs 1 · 04_infer 5 · 05_emit 3 · 05_emit_rust 8
  compile 1 · complexity 6 · trait_derive_emit 4

trait_derive_emit was absent from the note's list entirely, so the clause was wrong about
the population's membership and not only its size.

NOTHING EDITED THE NOTE. The tree moved underneath it, which is precisely the decay mode
DESIGN §3 gives for a positional citation -- it rots without anyone touching either end --
and it is what the 2026-08-24 ruling forbids by name: cite the instrument, never transcribe
its output. The recipe is one grep and it is now stated instead of its result.

THE ARGUMENT NEVER NEEDED THE NUMBER, which is the part worth keeping. What makes this the
seed's idiom rather than a new shape is that EVERY such collector recurses itself, and that
holds at 16, at 29, and at whatever it measures next. A clause whose force depends on a
figure it cannot keep current was overstating its own evidence -- the number was doing
rhetorical work, not logical work.

Two derived ordinals went with it. "the 17th instance of a 16-instance idiom" and
"collect_explicit_return_values is the 17th ... the 18th" were positions in the disproven
count, so they were already false; they now read as further instances with no ordinal. An
ordinal is a transcribed measurement wearing the costume of a structural fact, and it is
worse than the raw count because it does not look like a measurement at all.

Prose-only, in one data row. No semantics, no behaviour, no gate.

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

* Regenerate the stage0 mirrors, and delete the dead child_type_at accessor

REGEN. The prose change in 04_infer edits two `data ...: String` rows. Those are program
data, not annotations, so they emit into the stage0 Rust mirror, and CI's build lane
refused with:

  required-regen: FAIL generated surface drift: v1_compiler_infer.rs

Regenerated through the sanctioned producer -- `claim_executor --required-regen
--source-root dag --source-root src/v2` -- rather than hand-edited. A hand-authored mirror
is exactly what that gate exists to refuse, and its only reachable green would have been
the forbidden action.

EVERY CHANGED LINE IS ACCOUNTED FOR, because a regen can also delete orphan content a
committed projection carries that no authority produces:

  v1_compiler_infer.rs        2 lines   the two data rows edited in the parent commit
  v1_compiler_infer_types.rs  14 lines  deleted: the child_type_at body

Nothing else moved. Re-running regen against the installed mirrors reports
first_generation_equal=true. (declared_divergent=1 [main.rs] is pre-existing; it is present
in the failing run on the parent commit too.)

DEAD ACCESSOR. v1.04_types child_type_at had ZERO callers -- measured across the whole
corpus, not just .dag: one definition in 04_types.dag, one in the generated mirror, no
consumers, no re-export, no prose reference.

It is deleted rather than left because of where it sits. It is a decoy beside
child_type_node, the live accessor that discriminates a type child from a field child by
whether `inferred` is populated -- a fabricated provenance stamp the parser writes at parse
time. Anyone repairing that discrimination reads both functions and has to work out which
one matters. Approved by compiler direction as needing no ruling.

WHY THIS WIDENS AN ALREADY-APPROVED PR, stated because the usual answer is that it should
not. #9462 was red and required a regen commit regardless, so the approval resets either
way and the deletion rides along at zero marginal cost -- and it keeps this to ONE regen
cycle rather than two. Without that, the correct call would have been a separate PR.

No semantics, no behaviour, no gate.

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

* The sibling row carried the SAME disproven count -- one sentence fixed, the claim left standing

FOUND FROM OUTSIDE, NOT BY ME. The first commit repaired explicit_return_conformance_note and
left seed_node_traversal_frontier asserting the identical thing a few lines above it:

  "the idiom is 16 self-recursive `children |> flat_map` sites on origin/main across
   04_emit_info, 04_sigs, 04_infer, 05_emit, 05_emit_rust, compile and complexity"

Same 16, same seven-module list, same two errors -- the tree measures 29 across EIGHT, with
trait_derive_emit absent from the list entirely. I edited a SENTENCE when the defect was a
CLAIM, which is the document-wide-correction failure, committed inside the change whose whole
subject is a rotted figure.

THE SECOND COUNT IN THAT ROW GOES TOO, AND THE REASONING IS THE INTERESTING PART. It carried
"579 direct Node-storage field reads in 04_infer alone". A plausible reconstruction -- counting
`.children`, `.params`, `.inferred` and their siblings -- returns roughly TWICE that. That
establishes the number is STALE without establishing what the right one is, because I cannot
recover the recipe its author used.

So the repair is DELETION, not an update. Replacing a stale figure with one my own instrument
produced would swap an uncheckable number for a checkable-LOOKING wrong one, which is worse:
the first is visibly unverifiable, the second gets cited as verified. The site population is
named by its instrument (grep the idiom under src/v1); the field-read population has no agreed
instrument and is stated as a SHAPE rather than a count.

That asymmetry is why the earlier commit deliberately left this figure alone, and why leaving
it was still wrong -- declining to invent a recipe was right, declining to remove the number
was not.

Mirror regenerated through claim_executor --required-regen. One line in v1_compiler_infer.rs,
which is the row above. Prose only.

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

* The counts were deleted as CLAIMS and kept as HISTORY -- which is the same decay inside the sentence announcing its removal

Found in review, not by me, and it is the sharper half of this PR.

The previous commits removed the rotted figures from both 04_infer rows as ASSERTIONS and then
restated them as provenance: "it read 16 sites across seven modules while the tree measures 29
across eight". That is still a number in a live `data … : String` authority. It rots the same
way the original did, nothing re-derives it, and it gets quoted back as though this row had
measured it -- so the row announcing that it no longer transcribes an instrument's output was
transcribing one in the same breath.

BOTH ROWS NOW CARRY ZERO FIGURES, verified mechanically rather than by reading:

  grep '^data explicit_return_conformance_note' | grep -oE '(16|29|579|18|17th|18th|seven|eight)'  -> empty
  grep '^data seed_node_traversal_frontier'     | grep -oE '(16|29|579|18|17th|18th|seven|eight)'  -> empty

The before-and-after lives in the PR, which is the artifact that is allowed to carry a
superseded measurement, because it is dated and nobody consumes it as current authority.

A SECOND, INDEPENDENT PREDICATE DEFECT, also named in review. Both rows pointed at a LEXICAL
instrument (grep `children |> flat_map`) while asserting SEMANTIC properties -- self-recursive,
and the seed's ONLY traversal idiom. A grep bounds the literal-occurrence population and cannot
establish recursion or exhaustiveness. Naming an instrument does not fix a claim if the
instrument answers a different question, which is the same right-number-wrong-subject failure the
counts themselves were. Both rows now say so: the grep bounds the literal population, and the
recursion property is read off the sites rather than off the count.

WHY DELETION AND NOT AN UPDATE, restated because it is the part a reader will want to argue with:
one row's field-read count has no reproducible recipe and a plausible reconstruction disagrees by
a wide margin. That establishes STALE without establishing CORRECT. Substituting a figure from my
own instrument would swap an uncheckable number for a checkable-LOOKING wrong one -- worse,
because the first is visibly unverifiable and the second gets cited as verified. That population
is stated as a shape.

Mirror regenerated through claim_executor --required-regen and applied from the candidate rather
than hand-edited; the diff is exactly the two rows, 4 lines, no other drift.

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

* Restore the mirror the merge resolution dropped: --theirs took main's bytes, which never carried the prose fix

THE MERGE CONFLICT WAS IN A GENERATED FILE and I resolved it with --theirs to complete the merge,
intending to regenerate immediately. That resolution takes MAIN's mirror, which by construction
does not contain this branch's edits -- so for one commit the authority (04_infer.dag) carried the
repaired prose and its mirror carried main's older text. A regen fixed-point check is exactly what
catches that, and it did:

  changed lines: 4, in the two rows this branch edits, nothing else

Mirror re-derived from the MERGED authority through claim_executor --required-regen and applied
from the candidate rather than hand-edited.

WHY THIS IS WORTH A COMMIT MESSAGE RATHER THAN A SILENT FIXUP: picking a side of a conflict in a
generated file is never a resolution, it is a coin flip between two stale artifacts. The authority
merged cleanly on its own -- the mirror had no business being adjudicated at all, and the only
correct answer was to recompute it. Taking --ours would have been equally wrong in the other
direction, dropping main's edits to the same file.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* An escalation is not an infrastructure loss: give ExecutionAttemptLineage the arm the resident-model thesis is measured on (#9546)

A local model that reaches a terminal result it cannot carry, followed by a
more capable model taking the next try, is the single observation the
"progressively smaller models suffice" claim is denominated in. Measured on
this tree, nothing could express it: grep for Episode/continuation/retry_of/
predecessor across dag/gunbc, dag/std and src/v2 returns nothing episode-shaped,
and ExecutionAttemptLineage's three arms are InitialAttempt, InfrastructureRetry
and RequestedReexecution. So an escalation had to be recorded as either an
infrastructure retry -- which says the work told us nothing -- or as an
unrelated initial attempt, which discards the edge entirely.

CapabilityEscalation is a sibling of InfrastructureRetry rather than an arm of
one generic Retry, because the two differ in exactly what lineage exists to
record: an infrastructure loss says nothing about the work, while an escalation
says the work exceeded the capability that was tried. Like its sibling it names
the prior attempt AND the receipt that established the prior result, so merely
resolving a more expensive model after a cheaper one is a selection fact rather
than an escalation.

The two arms are deliberately the same SHAPE, which is what the third witness is
for: a control checking only the prior-attempt key would pass identically
against a lineage that had collapsed them, so the discriminating assertion
matches on the arm and fails if an escalation ever reads as a retry or the
reverse.

WHAT IS NOT VERIFIED, stated because a green I cannot stand behind is worse than
no green. `gunbc compile` takes no --entry, and the whole-corpus run over this
tree reports 31139 diagnostics ON PRISTINE MAIN, 1283 of them "expected item
declaration" on `//` annotation lines -- so that CLI path does not route source
annotations the way the required parse phase does, and cannot adjudicate this
tree. My attempted discriminating RED (deleting one arm from an exhaustive
match) returned 31139, byte-identical to the pristine baseline: it added zero
errors and therefore discriminated nothing. An earlier local run appeared clean
only because it was killed at its timeout mid-typecheck and the truncated output
rendered identically to a completed clean one. CI is the check here.

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* Consume the modeled sidecar predicates instead of re-spelling them in Rust (review 56971 follow-up to #9499) (#9527)

* A typed wall for barren witness files exists, is wired to a hard failure, and the required floor never calls it: 62 unenrolled claims, the third scanner, and 37 promotions

The brief was 62 claims declared plain `fn` and never enrolled. Chasing why produced a
larger finding than the population: `v2.workflow.floor_naming_hygiene`
`floor_entry_is_barren_test_sidecar` has refused this exact class since it was written,
`floor_discovery_finalize` turns it into `FloorDiscoveryRefused`, and the host returns that
as `Err`. It stops the line. It has never been on the line.

MEASURED, not inferred: main run 33092582255 (headSha 107304a579), both lanes green, four
barren `*_test.dag` entries present at that sha, and zero occurrences of `barren` or
`sidecar` in the 693,975-byte run log.

WHY: `run_required_floor` builds its roster from `prepared.witness_files`, produced by
`witness_file_from_source`, which answers `None` for a file with no `test fn` — and the
caller discarded that answer. The walled `.dag` producer is reachable only through
`discover_floor_witness_roster`, which the required floor never calls. Three scanners for
one fact live in one binary and the wall guards the one production retired. The Rust test
asserting the wiring is not the missing piece: it still PASSES, because the wiring is
intact on the producer path — a green local `cargo test` says nothing about the required
path.

WHAT LANDED: preparation records the discarded fact; the floor asks
`floor_naming_hygiene`'s own `floor_test_sidecar_suffix` which recorded paths are
`*_test.dag` and refuses `cause=BarrenTestSidecar`. The rule keeps one home; only its
consumer moved. The recorded set uses the RULE's vocabulary — neither `test fn` nor
`test data` — so the 13 test-data-only files are not over-refused. The floor's summary line
is bounded above rather than left exact-and-silent. 37 leaf claims promoted, 37/37 PASS,
and the 4 sibling-conjunction aggregates deleted: each was a hand-rolled substitute for
enrolment with exactly one occurrence in the corpus.

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

* Consume the modeled sidecar predicates instead of re-spelling them in Rust: delete the forked suffix test and the added test-decl scan

review 56971 requested changes on #9499 and was right on both counts; #9499 merged before
the rework landed, so main currently carries the fork and this is the repair.

FINDING 2, the reimplemented predicate. `floor_barren_test_sidecars` read
`floor_test_sidecar_suffix` from the `.dag` and then applied `strip_prefix("./")` and
`ends_with` in Rust. Reading the constant does not make the computation derived from the
authority — the two can drift independently. It now INVOKES the modeled predicates and
decides nothing itself. My own framing ("policy stays home, only the consumer moves") was
the error: I moved the CONSTANT home and left the COMPUTATION forked.

FINDING 1, the added test-declaration scan. The `!line.starts_with("test data ")` check is
DELETED. It existed to stop the wall over-refusing the 13 test-data-only files, which is
exactly what `floor_discovery_scan_test_decl_names` already does inside
`floor_entry_is_barren_test_sidecar`.

THE SHAPE, and why it costs one call rather than one per corpus file — which is what pushed
me into the fork to begin with. Preparation records a CANDIDATE SET, not a verdict: every
source `witness_file_from_source` declined, asking nothing about suffixes and nothing about
`test data`. `floor_entries_requiring_test_sidecar` (new, in `v2.workflow.floor_naming_hygiene`,
composing the existing `floor_entry_requires_test_sidecar`) is then asked ONCE for the whole
roster — a pure string question, one crossing — and `floor_entry_is_barren_test_sidecar` is
asked per survivor with that file's content, typically zero or a handful of invocations.

THE CANDIDATE SET IS DELIBERATELY OVER-INCLUSIVE AND THAT IS WHAT MAKES IT SOUND: a
test-data-only file lands in it and the `.dag` answers NOT barren, because its own scan counts
`test data` as a test decl. Rust can only widen the question, never decide it, so a
Rust/`.dag` disagreement cannot produce a wrong refusal — only a candidate the authority
discards. A missing candidate source is a typed refusal rather than a skip (§5).

RE-VERIFIED BY EXECUTION, because changing the mechanism invalidates the evidence for it.
Same binary, corpora identical except `filesystem_read_outcome_witness_test.dag`:
RED refuses `cause=BarrenTestSidecar count=1` naming it; GREEN completes site-projection
(sites=13351 files=1697 claims=11910). The first re-run attempt failed loudly with
`no declaration named 'v2.workflow.floor_naming_hygiene.floor_entries_requiring_test_sidecar'`
because the control trees came from HEAD while the new `.dag` function was still uncommitted
— a binary/corpus mismatch the control caught rather than one that shipped.

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

---------

Co-authored-by: Brian Searls <bts53@scarletmail.rutgers.edu>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brian Searls <briansearls1@gmail.com>

* Delete the floor's stale live-tree decline (#9106)

* Delete the floor's stale live-tree decline

* Enroll surfaced required-floor dispositions

* Retire executing witnesses from deferral freeze

* Retire routed witnesses from deferral freeze

* Retire merged route gaps from deferral freeze

* Enroll post-merge shell route gaps

* Enroll activated semantic reds

* Place expected-red provenance at module grain

* Enroll newly exposed parser-drop route gap

* Adjudicate live-tree cut witness fallout

* Keep quarantine disposition annotation at module grain

* Fix expected-red chunk merge boundary

* Declare the live-tree census debt

* Retire five executing freeze rows

* Retire two supplied route gaps

* Bind exposed floor debt to repair lanes

* Retire stale live-tree decline prose

* Close route-gap lists after stale-row retirement

* Retire repaired expected-red rows

* Classify realization floor non-verdict

* Compose discovery census with live-tree cut

* Declare the exposed gitattributes drift

* Bind the accumulator analysis explicitly

* Preserve new diagnostic histogram arms

* Update floor projection annotation

* Close floor cut review obligations

* Remove stale retained-parameter annotation

* Correct live-tree cutover annotations

* Retire repaired live-tree census stalls

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>

* R_X(B): the legacy use-line repair envelope, keyed on repair sites because emit-time candidates do not correspond to reference occurrences (#9447)

* R_X(B): the legacy use-line repair envelope, keyed on repair sites because emit-time candidates do not correspond to reference occurrences

The producer (#9439) correctly refused the binding envelope: its denominator is
the candidates that reached the decision, produced by the same pass that decides
them. This lands the envelope with a denominator that is not that.

The open question -- does every emit-time repair candidate correspond to a
parse-time reference occurrence -- is answered NO, in three independent
directions at once: the roster is deduplicated by SPELLING before any decision
(grain), it admits names merely for appearing as an identifier in the EMITTED
Rust (superset -- nothing authored them, so they can have no occurrence id), and
it drops occurrences the repairer correctly never touches (subset). So R_X(B) is
a PEER of O_X(B) keyed on repair sites, not an instance of it.

The completeness law is one law for any key, so it is hoisted key-generic into
std.observation_completeness and both envelopes instantiate it -- two subjects,
two denominators, one join. decl_field_label moves to std.decl_ref for the same
reason, with the third projection in std.observation named rather than tolerated.

Roster provenance is structural rather than ordered: SubjectRoster is
sole_constructor, prove_subject_roster is its only mint, and the admission takes
one -- so joining against an unproven roster has no spelling.

What this does NOT establish is stated in the carrier beside what it does: the
producer could still assemble the roster from the candidates it decided. The
tautology becomes visible and nameable rather than dissolved, which is an
improvement and not a proof; the next-rung trigger is recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Restore legacy_binding_delta's own occurrences: payload, over-renamed by the hoist's blanket sed

The hoist renames the completeness arms' payload from `occurrences:` to `keys:`,
because at the repair envelope's instantiation the key is a repair site and
"occurrences" would be a lie. `{ occurrences: ... }` also spells the payload on
six unrelated ProvenanceTotality arms in legacy_binding_delta, and a blanket
rename over the witness took those with it -- 71 blocking errors, none of them in
the module the hoist was about.

Caught by compiling the blast radius rather than grepping it, which is the whole
reason it was compiled: "two witness files" is a file count, not a symbol
census, and the payload name was never the thing being renamed -- the TYPE was.

* Rename the roster carrier off a name the enforcement lens already owns, and drop the declaration move out of this change

Three CI failures, three causes.

SubjectRoster was already declared by v2.lens.enforcement.vocab for an
unrelated concept. Whole-corpus resolution handed THIS type to that lens's own
consumers and their `entries` field stopped existing -- nine diagnostics, none
of them in a module this change touches. Renamed to ProvenRepairRoster. The
shape is the finding rather than the fix: the duplicate was minted here and
every symptom surfaced elsewhere, so no compile of this closure could have
shown it, which is what makes "my closure is clean" structurally unable to
catch this class.

decl_field_label's move to std.decl_ref is reverted. It caused both the regen
drift on std_decl_ref.rs and two TargetChanged wave-admission deltas. The
declaration stays in the binding envelope and the repair envelope imports it --
one authority, no fork -- and the relocation lands as its own change where its
two rows are the whole reviewable diff.

The first cut of that annotation justified the revert by citing the wave grain
note's "two change classes in one diff" clause. That was a mis-citation: the
clause's subject is a wave that BOTH REQUALIFIES AND MOVES a symbol, and this
requalifies nothing. Corrected in place rather than dropped, because a carrier
that once stated an invented prohibition should say so.

One unused import removed (ObservationCompleteness in the observation witness).
The remaining two UnexplainedSubjectMotion deltas are a confirmed defect in the
wave-admission channel's reader, owned by another lane; its refusal is left
standing rather than cleared by an admission row, which over a channel that
cannot see the reference would be a manual override rather than an admission.

Evidence: 21/21 witness arms return true; mutating prove_repair_roster's digest
comparison to a constant turns the provenance arm false while the positive
control stays true. All three affected closures compile at 0 blocking.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Merge main, and take the three obligations #9440's landing created

crisp-crab's #9440 merged first, so by the order the two lanes committed to,
this change owes the collision resolution -- and owes it HERE rather than in a
follow-up, because declaration names bind closure-globally and two declarations
of one name on main is a collision, not a shadowing. Neither author can observe
it by compiling their own branch: both were green against main independently.
The receipt is this lane's own SubjectRoster duplicate, which produced nine
diagnostics, every one in v2.lens.enforcement modules that change never touched.

Three obligations, all measured rather than assumed:

  - the placeholder `type CompleteLegacyRepairObservation<R>` is deleted from
    v2.workflow.legacy_baseline_capture and the real carrier imported from
    v2.workflow.legacy_repair_observation. Its accepted arm LegacyBaselineCaptured
    is constructible for the first time; the annotation is rewritten to record
    why the deletion could not wait rather than left describing a hole that is
    now filled.
  - the two LegacyObservationCompleteness references the hoist renamed --
    the import member and the LegacyBaselineObservationIncomplete payload --
    migrated to ObservationCompleteness<Int>. crisp-crab measured their exposure
    at exactly two lines and named both; both appeared where they said.
  - the second type parameter survives the swap deliberately. O is what the
    resolver selected per occurrence, R what the repairer decided per repair
    site; one parameter would force the emitter's repair vocabulary to equal the
    resolver's binding vocabulary, which is the conflation the operator ruling
    forbids, committed in the parameter list instead of the fields.

NOT carried: #9440's three dead imports. The offer was withdrawn after the
coupling was priced -- they are inert, nothing waits on them, and tying someone
else's cleanup to this branch's blocker was never the cheap option.

v2.workflow.legacy_baseline_capture compiles 0 blocking after the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Merge main: pick up the wave-admission membership fix (#9490) and the repair-decision producer (#9439)

#9490 splits membership_declared from membership_bound_through, so an authored
import claim answers the ADD direction outright. Both UnexplainedSubjectMotion
rows this branch was refusing on carry an explicit import claim naming
std.observation_completeness, so both close without the gate having to reach a
pattern arm or an inferred-slot field type.

#9439 landed the producer this envelope was built for: reference_derived_
candidate_disposition and reference_derived_census in v1.05_emit_rust. The
correspondence finding this branch rests on was read off that pass, and it is
now on main rather than on a branch -- so the annotation citing it names a
declaration that resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse a malformed denominator: a roster naming one site twice certified as complete (review 56949)

`std.observation_completeness` returned `ObservationComplete` for expected
`[A, A]` against observed `[A]`. Nothing missing -- A IS present, so both
expected entries filter out. Nothing foreign. Nothing repeated -- the repeat
test counts OBSERVED occurrences and there is one. So the envelope certified
exactness over a denominator that asked for one site twice.

All three refusals judged the ANSWER set. None judged the QUESTION set, and an
ill-formed question set defeats all three at once.

WHY 21 ARMS MISSED IT: every arm varied the OBSERVATION against a well-formed
roster; none varied the ROSTER. A missing AXIS, not a missing case within one --
and the module header already said completeness is a join between two sets while
every arm exercised one of them. The near miss that hid it: `[A,A]` answered
`[A,A]` DOES refuse correctly as repeated-observed, so the obvious fixture finds
nothing. Only the answered-once case slipped.

REPAIRED AT TWO LAYERS, and the receipt shows neither substitutes for the other:

- `ObservationRepeatedExpected`, checked FIRST. The other three arms are
  statements ABOUT a question set and are meaningless without a well-formed one;
  answering "missing" here names the OBSERVATION as the defect when the ROSTER
  is, sending a consumer to fix the wrong artifact.
- `prove_repair_roster` refuses a duplicate outright, so a `ProvenRepairRoster`
  cannot HOLD one -- construction at the mint rather than validation at the join.

The generic arm is NOT dead after the proof-side wall: the law is key-generic and
`legacy_binding_observation` derives its expected list with no proven roster, so
the arm is reachable from that consumer's denominator. A quiet guard, not a
decoration.

MUTATION RECEIPT, two independent mutations in sequence (not overlapped):
deleting the law's check reds 2 arms and leaves the proof arm TRUE; deleting the
proof's refusal reds only the proof arm. Unmutated, 24 arms green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Carry the repeated-expected arm into the two binding witnesses the new variant also made non-exhaustive

`ObservationRepeatedExpected` landed with arms added to the repair envelope and
its witness, and NOT to the two binding witnesses that match the same
key-generic law. Six matches, one arm each.

WHY IT REACHED CI: the local check was `v1_src_dag_parse`, which returned
`4210 file(s) parse-clean` and was read as evidence the tree was well-formed.
Exhaustiveness is a RESOLVE-time judgment, so a parse sweep can never see it --
parse-clean and resolves are different claims about different phases, and the
green one was not about the thing being changed. The verification is now a
resolve of each affected witness, which reproduces the six diagnostics when the
arms are absent and passes when they are present.

The variant refusing every exhaustive match across three files is the substrate
doing its job -- nothing could have silently kept the old vocabulary. What
failed was my check, not the wall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brian Searls <11205878+briansrls@users.noreply.github.com>

* scm: a typed read-command result that names no destination (#9443)

* scm: a typed read-command result that names no destination

Recut onto current main so the diff matches the scope this PR claims.

WHY THE RECUT. The previous composition reached main through the save
branch's ancestry, so it carried repository_save.dag and its witness --
neither of which is on main, and both of which belong to #9434, which is
draft. Merging this PR would therefore have landed the parked save half
as a consequence of branch topology rather than as a decision anyone
made. Nobody would have done anything wrong; the ancestry would simply
have outranked the park.

The park is respected rather than routed around. No convert_to_draft
event exists on #9434 and draft is not this tooling's default, so the
hold is unexplained rather than accidental -- and the correct response to
an unexplained hold is to leave it standing.

WHAT REMAINS, and why the load refinement is not scope creep: splitting
RepositoryLoadRefusal out of RepositoryLoad is what lets
ScmReadRepositoryUnavailable carry a refusal that CANNOT hold a success.
Without it this module's unavailable arm would be constructible holding
RepositoryLoaded, with nothing to refuse the pair. It is the enabling
half of this change, not a neighbour travelling with it.

The dependency runs one way, checked before cutting: the save witness
imports RepositoryLoadRefused, and nothing read-side references save. So
dropping save costs this PR nothing.

Both keystones return `true` on this tree over current main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop the refusal-path accessor: the third instance of a shape this PR
documents as twice-removed

`repository_load_refusal_path` had exactly one occurrence in the tree --
its own definition. No consumer, so DESIGN section 6 residue.

WHAT MAKES THIS WORSE THAN ORDINARY DEAD CODE: read_command.dag's own
header, in this same PR, cites this exact helper shape being removed
twice before -- once as `checkout_succeeded`, once from
`gunbc.scm.ancestry` under review 56207 -- and states the reason that
survives. This change re-added the third instance while documenting the
first two. Neither a lens nor a green run can see that; only reading the
two files against each other does.

The surviving rationale in the deleted comment block was about the TYPES
(why no `loaded: Bool` exists), not about the accessor, so it moves to
`type RepositoryLoad` rather than being deleted along with the function.
A prose row removed for one reason must not silently take its contents
with it.

It gains the reason the shape keeps recurring, which was written down
nowhere: with no consumer the accessor is residue, and WITH one it is
worse -- a fourth refusal arm would be absorbed by the projection instead
of failing to compile at each site that must decide about it.

Both keystones return `true` after the deletion.

Reported by review 57012.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Enroll the three read-command route gaps the floor actually reported

The floor reported route_gap_unenrolled=3 on this branch, all three in
scm_read_command_witness, all with one cause:

  the hermetic route has no arm for Read (operation declares no
  mock_response)

  scm_rc_an_absent_repository_is_unavailable_not_an_empty_answer
  scm_rc_status_refuses_in_the_same_arm_on_the_same_path_as_log
  scm_read_command_keystone_holds

Same boundary already recorded for the load witness: extdeps.filesystem
declares no mock_response, so the hermetic frame has no arm for a FAILING
read. All three claims exercise the absent-repository path, and the
keystone inherits the gap by composing them. They pass under `gunbc run`,
which performs the real read; they cannot reach their subject hermetically.

MEASURED, NOT PREDICTED. These were foreseeable and were deliberately NOT
pre-enrolled: enrolling an identity that does not gap is a stale row and
reds the build, which is what stale_route_gap counts. The rows are added
now because a run reported these exact three identities.

Enrolment records the gap as known debt. It does not make the gap
acceptable and it is not a fix: the remedy is a hermetic arm for a failing
read, which belongs to the filesystem boundary and not to this PR.

Roster 112 -> 115; the module still evaluates and returns its list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

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>

* Anchor a relative source root the same way in both module-index builders (#9548)

* Anchor a relative source root the same way in both module-index builders

The two builders disagreed on how a relative source root resolves:
try_build_module_index anchored through anchor_source_root (process
workspace), while try_index_source_root_into_module_index read the string
straight off the filesystem (process CWD). One concept, two answers,
selected by which builder a caller happened to reach.

The fork survived because the one place it is observable is the one place
nothing was asserting: every CI invocation runs with its CWD at the
workspace root, where both spellings denote the same directory.

A root that cannot be anchored still falls through to the existence
refusal with its ORIGINAL spelling, so the diagnostic names what the
caller asked for rather than a rewritten form they never wrote.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Trigger a fresh merge ref against main after #9551

The merge ref is computed when the head is pushed, against main as it was
at that moment; main moving afterwards recomputes nothing, and a RERUN
replays the original pinned ref. #9551 landed the four emit mirrors after
this branch's last push, so its base predates the regen fix.

Empty rather than a local merge of main deliberately: main's change here IS
the generated mirrors, and merging it locally would mean hand-resolving
emitted files -- the one state the regen gate forbids. Pushing recomputes
the base without touching them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

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>

* Measure the fan decoder's execution identity, and refuse to call it bound (#9536)

* Instrument the decoder's execution identity by composing two things that already existed

gunbc.bmc_fan_program_interpretation modelled a decoder identity and refused to author
one, because both digests are properties of an execution and a hand-written pair would
be two literals typed by whoever typed the decoder -- agreeing because one person wrote
both, and continuing to agree after the thing they describe changed. This produces them
from an execution instead, which is what an operator adoption needs: a corrected decoder
that assigns different meaning to the same bytes must be distinguishable from a refactor
that assigns the same meaning.

NOTHING IS MINTED. Both halves were found by searching before building, which is why
this module is short:

  v2.lens.module_graph import_closure_live          enumerates the entry's closure
  tools.multi_module_compile_fixture compile_fixture returns a structural digest over
                                                    the (path, content) vector AND the
                                                    running compiler binary's own hash,
                                                    both host-computed from what ran

ONE NEAR-MISS IS DELIBERATELY NOT REUSED, and it is recorded so nobody "fixes" this by
adopting it. std.interface_summary typed_module_key is exactly the right shape -- a
source key combined with compiler identity -- at the wrong grain: its module_key folds a
module's source hash with its DIRECT IMPORT INTERFACE hashes. A body-only change in a
transitively imported module leaves it unchanged while changing what this decoder
produces; std.decimal canonical_exact_decimal could be rewritten and the key would not
move. It answers "may I reuse a cached typecheck"; this answers "is this the same
reader". Borrowing the first to answer the second invents the entailment rather than
misstating any fact.

THE DISCRIMINATING PAIR IS MEASURED, both halves, because an identity that moves on
everything and one that moves on nothing both look fine from a single run. Baseline
3282f082abc9d722 over 40 modules; one comment line added to std/decimal.dag, inside the
closure, moved it to 9bcfbd782022b120; one comment line added to gunbc/fleet_fan_wiring.dag,
outside it, left it byte-identical to baseline. Both restored byte-exactly. The compiler
digest held across all three.

The second half is the one worth having: a digest over the whole tree passes the first
test and is useless, since every unrelated edit would invalidate an adoption.

WHAT IS NOT CLAIMED. There is no enrolled witness, and the reason is structural rather
than neglect: the instrument reads the live tree and takes about four and a half minutes,
so the floor planner declines it, and the mutation half would have to edit tracked
source, which no hermetic witness may do. The evidence is a recorded measurement with its
controls -- weaker than an executing one, and said so rather than dressed up. The
next-rung trigger is a fixture-grain closure the instrument owns, at which point the pair
becomes an ordinary witness.

Cost is recorded too, because it decides where this may run: the closure walk alone is
about 4m28s. On demand only; nothing here is enrolled in a required lane, and a
four-minute live-tree walk on every push would be the corpus-denominated cost that gets
paid by every consumer wanting something else.

* Report the identity as measured-but-unbound, because nothing here proves the decoder ran on this vector

The side-chat raised the objection that matters and it is right. This instrument
enumerates a closure, hashes that exact vector, and compiles it. It does NOT execute the
decoder against that vector -- a semantic program digest is produced by some other run,
through the interpreter's own resolution of the same entry. Pairing this identity with
that digest would be two individually correct observations with an invented arrow
between them: the same fake join removed from the capture observer on #9299, one level
up.

The two subjects are very probably identical, since the walk follows the same import
edges the interpreter resolves. "Very probably" is what the objection is about. Nothing
here proves the interpreter received this vector and no wider one.

So the standing is not handed out from here. DecoderIdentityEstablished is what lets a
consumer treat two readings as same-reader, and granting it from a run that did not
perform the reading would restore the unbound claim under a name that reads as bound.
The binding is now its own three-state carrier, the measured-but-unbound arm names its
own gap, and the standing derived from it is still the absent one.

That is the instrument reporting what it has rather than failing. The obligation is
NARROWED rather than discharged: what was missing was any producer at all; what is
missing now is one execution that both hashes its source vector and runs the decoder
against that exact vector, returning the identity and the semantic result together.
That trigger is recorded on the arm.

* Sharpen the binding trigger to name the missing host capability

Looked rather than assumed, and the gap is larger than 'finish the instrument'. Two
routes could bind the identity to a decode and neither is reachable today.

EXECUTE-THE-VECTOR: the seed registers exactly one fixture builtin,
compile_dag_multi_module_fixture, which COMPILES a supplied (path, content) vector. No
builtin evaluates one. So running the decoder against that exact vector needs a new host
surface -- v1 seed growth, which the freeze admits only in service of the v2 self-host
program, and this is not that.

PROVE-THE-SUBJECTS-EQUAL: nothing exposes the RUNNING program's resolved module set.
module_declaration_facts reads the source tree, which is the same authority the closure
walk already consumed, so comparing the two would compare a reading against itself rather
than against what the interpreter received.

Recorded at this grain because a trigger that reads as small invites someone to just
finish it, find the capability absent, and close the gap with an argument instead -- which
is the invented arrow this carrier exists to refuse.

* Consume the decoder declaration instead of re-minting it (review 57069)

A byte-identical DeclarationRef for the decoder stood in this instrument
beside gunbc.bmc_fan_program_interpretation fan_program_decoder, which is
the module declaring the standing the instrument exists to discharge --
and which this module already imported from, so consuming it costs one
import member. Two authorities for one fact can drift independently
(DESIGN section 3); worse here than in general, because a drift would
identify a reader other than the one whose standing is at stake.

The entry PATH stays authored and is not the same fork: a module path
does not carry which source root stores the module, and deriving one
needs an observed ModuleStorageIndex rather than a pure transform. The
annotation now says so, so the next reader does not read the surviving
path as a missed half of this repair.

Measured after: identity unchanged -- 40 modules,
source_closure=3282f082abc9d722, compiler_runtime=f2c179fb7e10d373,
the same values the pre-fix baseline reported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Say that the typed_module_key grain claim is an argument, not a measurement

The note asserted as fact that a body-only change in a transitively
imported module leaves typed_module_key unchanged while changing what the
decoder produces. The reasoning is sound and has now been endorsed by two
reviews and one external adjudication -- which is exactly why it needed
correcting rather than leaving: convergent readings of one argument are
not independent evidence about that argument, and an approved PR carrying
an unmeasured claim stated as fact is the rung inflation DESIGN 4b(1)
names as worse than sitting low.

I tried to measure it and found the route blocked, so the note now carries
that instead of the assertion. The only live producer of import interface
hashes is v2.lens.interface_summary module_key_for_rel_path. It has zero
consumers in the corpus; the first attempt to run it refused with
export_signature_facts `empty authored type name` on
extdeps.shell.credentials env_credential -- a pattern returning an
anonymous record, one of three such declarations. So that lens's live path
is inert in the DESIGN section 6 sense: the machinery exists and the first
exercise of it does not work.

Authoring the two export lists by hand was rejected rather than
overlooked: the claim IS that a body edit leaves the exports equal, so
asserting that equality assumes what the control exists to establish.

Next-rung trigger recorded on the note. Identity re-measured unchanged
(40 modules, source_closure=3282f082abc9d722).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Delete three commentary String rows and the digests they transcribed (review 57083)

The review flagged typed_module_key_grain_note, decoder_identity_
discrimination_note and decoder_identity_cost_note as the section 4c
pattern DESIGN discourages -- pure prose duplicating the // blocks above
them -- and noted broad corpus precedent for it. Precedent is the reason
to fix it in new code rather than the reason to keep it: adding fresh
instances of a discouraged pattern because the corpus is full of them is
how a discouraged pattern becomes the convention.

DESIGN is stricter here than the remark was. Two of the three rows
transcribed digests and a wall time into prose, inside the very module
whose entry point re-derives them, which is the standing "name the
instrument, never transcribe its output" ruling and not merely commentary
debt. So the numbers are gone from the // blocks too. What survives is
the SHAPE of the controls, which does not rot: an edit INSIDE the closure
moves source_closure, an edit OUTSIDE it leaves it byte-identical, both
restore. Anyone wanting the figures runs report_decoder_identity, which
prints them with the module count.

The // blocks are kept where they carry irreducible rationale -- why
typed_module_key is the wrong grain, why no hermetic witness can hold this
pair -- which section 4c permits and which the String rows were only
restating.

Re-measured after: unchanged, 40 modules, same digests the instrument
prints.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

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>

* Witness evidence integrity: an undeclared live_tree_disposition silently declines the module — make silence REFUSE, burn down the 338, then the staged DeclinedLiveTree root deletion is safe (#9471)

* The live-read derivation detects a quarter of the readers it would have to replace

`v2.std.live_tree`'s note named the nightly affected-set falsifier as the thing
that catches a row declaring `SubstrateInputsOnly` while reading live state.
That cadence was deleted by the floor cut (#8283) and the repository carries
three workflows, none of which runs a predict-only cold comparison. The same
note stated the undeclared fail-closed default as THE fact, while the required
floor's own scan defaults the identical silence the opposite way -- so a reader
asking what happens to a witness that declares nothing was told the half that
withholds it, and the consumer that actually executes admits it.

What survives as the backstop is `effect_reach_derived_reads_live_tree_for_entry`,
and it does not derive this fact. It answers host-reading only when two
INDEPENDENT existentials both hold somewhere in the import closure -- some file
carries a repository path literal, some file carries a host-sink call shape --
so a closure that performs a real `Filesystem.Read` and names no path answers
false, and two modules that never call each other supply the two halves between
them. The ceiling is filed as a §4b row on the derivation's own authority, with
its next-rung trigger naming the capability (call-reachability-grade per-witness
classification) rather than an artifact that would contribute to one.

The evidence is a planted pair rather than a corpus count: two fixture entries
differing by exactly one import edge whose only content is a path-literal row,
performing a byte-identical read. The positive control derives host-reading; the
sink-only entry does not. Authored both sides, so the red is a property of the
derivation and cannot be dissolved by corpus drift.

The consequence runs opposite to the standing objection that an authored
disposition duplicates a derivable fact. `reads_live_tree_effective` consults the
declaration FIRST and reaches the derivation only for a row already claiming
SubstrateInputsOnly, so the derivation is the sole thing between a lying row and
a predict-skip. Replacing the declaration with it would be a scope narrowing
wearing a construction argument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* The derivation's third bound: an unreadable closure file is skipped toward ADMIT

Confirmed by reading effect_reach_derived_reads_live_tree_for_closure_paths: a
closure file that cannot be read hits a bare `continue`, leaving both flags as
they were. Every unreadable file therefore biases the accumulation toward false,
which biases toward admitting a row that claims SubstrateInputsOnly -- the one
arm that could notice its own blindness discards it, in the direction that
weakens the only thing standing behind a lying declaration.

It compounds the empty-adjacency bound rather than sitting beside it: where the
closure is the entry alone, one failed read leaves the loop having seen nothing.
And unlike the conjunction and the adjacency, which are properties of the corpus
and measurable today, this one is a property of the run, so its magnitude is
whatever the filesystem did that time and nothing records it.

Found by swift-badger-524.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Route the ceiling row onto the ladder vocabulary instead of a prose blob

Review 56493 observed that the §4b class row was a large `String`, which §4c
names as misplaced data, and reported that no typed §4b carrier existed to route
into. One does: `gunbc.guarantee_rung_drop` declares the closed `GuaranteeRung`
vocabulary, and `gunbc.hermetic_mock_fidelity` already files a discovered class
in exactly this shape -- typed rung and ceiling, closed-coproduct reasons, and
rationale left in annotations beside the row.

So the row follows that pattern rather than minting a class of its own. The three
bounds become a closed coproduct, because naming them is what lets the class be
recognised a second time; enforcement becomes two reachable arms rather than a
sentence; and the next-rung trigger is DERIVED by a total function over the
coproduct rather than stored, which makes a trigger-less row unwritable instead
of merely checked. That all three bounds derive the same trigger is the finding,
not a redundancy: the capability replaces the approach rather than patching a
term.

The record is named for its subject. No corpus-wide §4b carrier exists, and
minting one from a single instance would put a second authority beside
hermetic_mock_fidelity -- the ladder VOCABULARY is the part that must not fork,
and that is what is reused.

Verified by execution: `gunbc compile --entry src/v2/std/effect_reach.dag`
returns 0 blocking errors, 21 files emitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Record that stamping the silent population was built and dropped

A future reader who finds the fail-closed default and the 24.7% backstop
measurement will reach for the obvious move -- stamp every silent witness file
-- and there is nothing in the tree telling them it was already tried. It was:
520 files stamped, silence made a typed located refusal on both consumers, then
discarded because the floor's decline arm was already being deleted at its root,
which is what made a truthful ReadsLiveTree stamp cost coverage in the first
place.

The note records the reason rather than the fact, because the reason is what
transfers: the arm's deletion is the enabling event for a truthful stamp, not
its reward, and the question revives when the selection consumer acquires an
enforcement it currently lacks -- not when the silent population grows.

Suggested by swift-badger-524, who observed the work would otherwise be visible
only in a reflog and one message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Sweep the dead-cadence enforcement claim from all three of its homes

One claim lived in three places: v2.std.live_tree's disposition note, the same
module's stamp_provenance row, and a mirrored comment above
parse_entry_live_tree_disposition in cli_run.rs. Each said the nightly
affected-set falsifier catches a row declaring SubstrateInputsOnly while reading
live state. falsifier.yml was deleted by the 2026-08-15 floor cut. Correcting one
home leaves the other two as authorities for a false claim, so all three move
together.

The stamp_provenance row gets more than a past tense, because its consequence is
specific: the 2026-07-11 batch is machine-vouched rather than author-vouched and
inherits the deleted classifier's blind spot -- a live read hidden behind an
import was invisible to entry-text scanning. Those stamps were admitted on the
promise that a cadence would catch them if wrong. That promise is now UNMET, not
merely unfulfilled: nothing verifies a stamp, and one that was wrong the day it
was written is still wrong and still unobserved.

Three other authorities carry the same claim and belong to other owners; they are
deliberately not in this diff.

Verified: gunbc compile --entry src/v2/std/live_tree.dag -> 6 files emitted,
0 diagnostics.

Sites located by swift-badger-524.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Turn the supply fold from an honest screen into a real cross-mode selector: the six inputs supply.dag names as missing (#9472)

* wip: cross-mode supply selector

* wip2

* wip3

* wip4: duration state, commitment horizon

* review: fail-closed unbounded-duration availability, quote billing basis projection, Second-typed axis params

* witness: choose the window that the previous fold actually admitted

* Answer the new affordability arm in the sibling fabric witness suite

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>

* Make absence-from-a-failed-read unrepresentable: the listing ruling was prose, and prose reproduced the defect it forbids (#9564)

* Make absence-from-a-failed-read unrepresentable: the listing ruling was prose, and prose reproduced the defect it forbids

Review 46148 ruled that absence is established by a successful listing and never
by a failed read. The ruling was written as a `data ... : String` note, which
DESIGN section 4c calls commentary no `Accepted` program can read -- and it was
then violated in gunbc.deploy_transition, authored beside the note, where a
present-and-unreadable marker rendered as absent and so as PERMITTED at the belt
seam (#9561). A note is not a mechanism.

extdeps.filesystem.filesystem_io gains a carrier with one mint.
FilesystemEstablishedAbsence is sole_constructor; its only mint takes a
FilesystemDirectoryListing, itself sole_constructor and minted only from a
listing whose success channel was true. A module deciding absence from a read
alone has no value to return and no way to build one. filesystem_entry_presence
and filesystem_file_observation are the folds: presence is decided by the
listing, the read is consulted only for an entry the listing named, and every
way of not establishing absence lands in one indeterminate arm.

Consumers, so this is not a carrier with no consumer:
- gunbc.roadmap_verification_receipt, both walks. Already correct by hand; they
  now consume the carrier instead of restating the rule, and their private
  second spelling of List's wire encoding is deleted for
  filesystem_listing_names_entry.
- gunbc.devboot.build read_text_file,…
briansrls pushed a commit that referenced this pull request Aug 28, 2026
…write (#9434)

* scm: load a repository from a path, with the three failure owners kept apart

gunbc.scm.repository_envelope decodes a JsonValue and is pure. This adds the
effectful seam above it -- path to bytes, bytes to document, document to
envelope -- so the decoder stays testable without a filesystem and the read
path's only host effect lives in one place.

RepositoryLoad has four arms because the failures have three different OWNERS:
the operator's (wrong path), serialization's (not JSON), and the repository
schema's (JSON that is not a repository). Collapsing any two is the
not-applicable-rendered-as-malformed mode -- they have opposite remedies, and
telling a user their repository is corrupt when they mistyped a path sends them
looking for damage that is not there.

There is deliberately no `Absent => empty_repository()` arm. That is the
empty-observation narrow: it renders "I could not observe anything" as the
verdict "there is nothing here", which would make `log` print an empty history
for a repository that exists at a slightly wrong path, and `commit` mint a
first commit into a repository that already has a hundred.

EVIDENCE, by execution rather than by typecheck. Four fixtures drive the four
arms; each claim asserts the arm it reached, so collapsing any two fails here.
Mutation receipt: replacing the `read.success` consultation with a constant --
i.e. inferring absence from empty content, which an absent file and an empty
file both produce -- turns EXACTLY ONE claim red
(scm_rl_an_absent_file_is_unreadable_not_empty), with the other three still
green and the restored control green again. The success channel is load-bearing
and the absent-file arm is covered rather than decorative.

The absent-path fixture is under the checkout root deliberately: a /tmp path
would be refused by the interpreter's hermetic carve-out as a host effect
rather than answered as a failed read, and the claim would be measuring the
sandbox instead of this module.

No LoadStanding projection: that vocabulary answers what a loader may do at a
publication boundary, and three of these four arms have no publication meaning.
A consumer that is at one calls repository_envelope_load_standing on the decode
cause it holds. Manufacturing a standing for "file missing" would invent the
entailment DESIGN names as authority substitution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* scm: save a repository to a path, with the adjudication ahead of the write

WIP pending probe verification; mirror of repository_load, split as json parse/emit are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* scm: route the read through filesystem_read_outcome rather than raw projections

Review on #9433 (review 56583) noted this module consumed Filesystem.Read's
success/content/error projections directly, making it one more site against
filesystem_read_outcome_adoption_standing -- mitigatable, 91 unconverted, whose
next-rung trigger is that population reaching zero. Non-blocking under that
standing, but this module is NEW: it would have been the 92nd site, moving a
tracked population the wrong way for no reason.

It is also a coherence fix rather than only a debt one. The standing exists
because content+success+error nonsense combinations stay writable at unconverted
sites, and the distinction the fold preserves -- that an absent file and an empty
file differ only on the success channel -- is exactly the distinction this
module's four arms exist to preserve. Consuming the raw projections while the
header argued against conflating channels was incoherent.

Behavior is unchanged, verified rather than assumed. The four claims execute
green after conversion, and the mutation red still discriminates: feeding the
fold a constant `success: true` -- the converted spelling of the same defect --
reds EXACTLY scm_rl_an_absent_file_is_unreadable_not_empty, with the other three
green and the restored control green again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* scm: correct a routing claim in the save witness that measurement refuted

The header asserted that a successful save's write "would not route in this
frame". Measurement says otherwise: under `gunbc run` the mutated save really
did write the file -- which is how the order claim's control stayed red after
the source was restored.

What is actually established is narrower than either the old claim or its
opposite: a write executes under `gunbc run`; whether the required floor's
hermetic frame admits one is a different question and was not measured. A read
of a checkout path has an input carve-out; a write has no equivalent. Asserting
either answer without measuring the floor would be the rung claim DESIGN
forbids, so the header now names what was measured and what was not.

The mutation receipt is recorded in the header too, because it is stronger than
a passing pair: removing the adjudication from ahead of the write reds BOTH
claims, and the order claim stays red after restore because the file it says
should not exist now does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* scm: carry the written byte count as a ByteSize, and refuse the crossing that would fabricate one

review 56828 (REQUEST_CHANGES): `RepositorySaved { bytes_written: Int }` is a
flat-scalar unit field in a `gunbc.*` product model -- the unit lives in the field
name while the type is a bare `Int`. The raw scalar is legitimate at the
`extdeps.filesystem` boundary under cited-spec fidelity; propagating it inward is
what the rule forbids, and that is the step this type took.

Taken the wrap rather than the tracked marker the review also offered: `ByteSize`
(`std.measure`, `Measure<Memory, One, Nat>`) already is the authority for a
quantity of bytes, so a marker would have been debt registered where construction
was available.

THE WRAP FORCED A DECISION THE REVIEW DID NOT ANTICIPATE, and it is the reason for
the fourth arm. `ByteSize` counts a `Nat`, the host reports an `Int`, and the only
available crossing (`std.checked_arithmetic` `nat_magnitude`) takes an ABSOLUTE
VALUE. Applying it to a negative would turn a nonsense host report into a
plausible byte count -- fabricated plausible output in one line, in the module
whose whole point is that adjudication precedes the write. So the negative is
refused as its own arm carrying what the host actually said, and `nat_magnitude`
runs only on the non-negative domain where it is the identity.

`RepositoryWriteByteCountUnrepresentable` is a boundary obligation, not a class on
the ladder: external reality observed, typed, admitted or refused. No next-rung
trigger, and no witness -- reaching it needs a host write that succeeds and then
reports a negative, which no `SubstrateInputsOnly` fixture can author. The
annotation records that as a limit rather than leaving it to read as a gap,
because the alternative to the arm is not "no arm" but a silent magnitude.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* scm: enrol the save witness -- same defect review 56857 found in the load witness

Same class as review 56857, found by sweeping my own lane rather than waiting for a
second review to name it: every `-> Bool` claim here was a plain `fn`, so witness
discovery never enrolled any of them and the required floor never asked.

Two claims plus the keystone promoted. `scm_sv_unrepresentable_repository` stays a
plain `fn` -- it returns a `RepositoryEnvelope`, it is a fixture builder rather than
a claim, and promoting it would enrol something that asserts nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Adapt the save witness to the split refusal type that landed with #9443

#9443 replaced `RepositoryLoad`'s flat refusal arms with
`RepositoryLoadRefused { cause: RepositoryLoadRefusal }`. This witness
still matched the flat `RepositoryFileUnreadable` arm, which no longer
exists at that position, so it broke the moment #9443 merged.

This is the ordinary adaptation to a landed refinement, not a defect in
this branch. It was pre-announced before #9443 landed, precisely so a red
arriving on a PR nobody had touched would not be misdiagnosed.

One import and one arm. The claim does not change meaning: it still
asserts that a refused save leaves no file, and it still discriminates on
the same refusal cause -- it now reaches that cause through the outer arm
that owns it, which is what the split exists to enforce.

The PR stays draft and parked; this only keeps it coherent against main.

Keystone returns `true` after the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Enroll the two save-witness route gaps the floor named on this branch

The floor reported route_gap_unenrolled=2, both in scm_repository_save_witness,
both the same cause already recorded for the load and read witnesses:

  scm_repository_save_keystone_holds
  scm_sv_a_refused_save_leaves_no_file
    -- the hermetic route has no arm for Read (operation declares no
       mock_response)

They surfaced now rather than earlier because the previous commit adapted
this witness to the split refusal type, so the claims reach the load call
they were always going to make; the absent-file check needs a FAILING read
and the hermetic frame has no arm for one.

Measured, not predicted: enrolled only after a run named these exact two
identities. Enrolling an identity that does not gap is a stale row and
reds the build, which is what stale_route_gap counts -- it stayed 0.

Enrolment records known debt and is not a fix. The remedy is a hermetic
arm for a failing read at the filesystem boundary.

Roster 209 -> 211; the module still evaluates and returns its list.

NOT ADDRESSED HERE, because none of it is this branch's: the same run
reports failed=47 and interrupted_before_verdict=44, none of them in any
scm_ claim -- ci_budget_tree_witness, doc_reachability_witness,
lifecycle_survivor_corpus_census and peers, plus CPU-budget preemptions.
That is an inherited main condition.

The PR stays draft and parked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

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>
Co-authored-by: Brian Searls <briansearls1@gmail.com>
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