Skip to content

Restore the stage0 arm of the generated-projection merge-attribute population, as a complement rather than a roster - #9284

Merged
briansrls merged 3 commits into
mainfrom
session/quiet-fox-449
Aug 26, 2026
Merged

briansrls merged 3 commits into
mainfrom
session/quiet-fox-449

Conversation

@briansrls

@briansrls briansrls commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

The driver protects everything except the artifact it was written for

.gitattributes gives merge=generated-artifact to about a dozen small files — itself, three
hooks, three workflow YAMLs, two generated .dag rows, DESIGN, ROADMAP and the plan docs. It gives
it to none of the 135 emitted stage0 Rust sources, measured at d311b1ceab:

grep -rln 'Generated by v1 compiler -- do not edit.' src/v1/stage0/src | git check-attr merge --stdin
  -> 135 files, 135 unspecified, 0 covered

So coverage did not fail to exist. It stopped reaching the artifact it was built for. DESIGN
records that the driver exists because taking the ours side dropped authority-derived bytes with no
conflict, twice, on #7836 — against the stage0 seed. That seed is exactly what is uncovered.

Why this is worse than "conflicts look ugly": the file blocks its own recovery

These are .rs files the seed compiles. A text merge lands raw conflict markers in them, so the
tree stops building (error: encountered diff marker). The driver's own printed recovery recipe
requires building gunbc from that tree. So the file blocks its own recovery — and blocks the
recovery of every other conflicted generated path in the same merge. The documented escape hatch
(docs/plans/generated-file-conflict-policy.md Lane 3) told you to rerun regen_stage0, a binary
#8406 deleted. neat-pike-374 hit this live merging main into #9244 and was forced to resolve
--ours by hand purely to obtain a compiler, which is precisely what the driver exists to prevent.

Root cause: an arm deleted at the root, and prose that kept asserting it

deterministic_generated_projection_paths() was committed_generated_artifact_paths() and nothing
else. The stage0 arm routed through gunbc.stage0_emit_plan, which the #8406 regen cut deleted at
the root. Nothing refused, because the consumer that needed it sat outside the deletion census —
the same shape DESIGN already declares for #8406's producer deletion, a second time. Two prose
carriers went on asserting the arm for months: gitattributes_emit_authority_note ("joining
committed generated artifacts with plan-derived stage0 Rust") and Lane 3 ("the derived paths feed
the merge-attribute population"). Both are repaired here, in the same change, because a note
asserting an arm nobody can execute is what let the gap stand.

The fix is a complement, and that is a stronger rung than any path list

The join named in the original brief is not implementable by this consumer, and that is a
finding rather than a detour.
derived_generated_stage0_repo_paths takes two live observations
— git ls-files and a git show of the emitted-population manifest — while expected_gitattributes
is pure and reached from artifact_generate, which the drift gate assesses purely. Wiring them
together would make a committed byte projection a function of somebody's working tree: two clones
at one commit could disagree about what .gitattributes must contain, and the gate would refuse or
pass on a fact about a checkout. neat-pike-374 raised the join, I objected on this ground, and they
withdrew it.

What lands instead is the model's own definition of the population, which is a complement:

src/v1/stage0/src/*.rs merge=generated-artifact     <- one glob
src/v1/stage0/src/<hand-maintained>.rs !merge       <- one unset row per crate-layout member

The header comment was tested as the population and rejected

// Generated by v1 compiler -- do not edit. disagrees with the model in both directions:
main.rs and std_lens_verdict.rs carry it and are hand-maintained, while compiler_tests.rs,
emitted_population.rs and v1_rt.rs lack it and are in the emitted-population manifest. The
complement gets all five right by definition.

Cross-authority receipt at d311b1ceab: 173 direct children minus 37 crate-layout members = 136;
the emitter's own declaration (src/v1/stage0/src/emitted_population.rs) names 137 direct children.
The two independently-derived populations agree exactly except main.rs, the single
emitter_produced_divergent_registrations row — a file the emitter emits whose committed bytes
deliberately diverge from it, so regeneration is not its recovery and the unset row is correct.

The window was open and used

Eight PRs merged in six minutes on the night this was found, several regenerating that mirror
(#9251 rewrote 544 lines of v1_compiler_emit_rust.rs). Each text-merged 135 unprotected generated
files. Main's floor is green on every completed run since, so nothing is known damaged — but
absence of a conflict was never evidence, which is the entire reason the driver exists. This is not
"no harm done"; it is "unmeasured".

Evidence

test.claim.generated_projection_merge_rows_witness, declared SubstrateInputsOnly — the
sibling test.claim.gitattributes_emit_witness reads the committed file, so it is ReadsLiveTree
and the required floor declines it: discovered, counted, never run. Every claim in the new
module is a function of the model alone, so it executes where that one cannot.

A gitattributes population is the canonical thing that goes green by being empty, so no claim
here is a count. Each names an identity and asserts a setting or a position:

witness goes red when
stage0_generated_source_glob_is_claimed the stage0 arm is absent (the defect, pre-change)
emitted_stage0_source_is_not_unset a named emitted source acquires an unset row
hand_maintained_stage0_source_is_unset the complement stops subtracting
registry_artifact_keeps_coverage an unset row removes existing registry coverage
glob_precedes_every_unset_row the rows are transposed (silent failure, no diagnostic)
rendered_order_places_glob_before_unset the rows are right and the renderer reorders
red_unknown_pattern_absent a pattern the model never derives appears

Per DESIGN 4b(4) these stay enrolled after they green: they are the regression controls that the
arm and its order remain real, not scaffolding that retires.

The wall that makes over-claiming safe, executed rather than read

The complement's one risk is a new hand-maintained direct child that nobody registers — it would
acquire the driver. That is walled by required_regen, and the wall fires. Executed by
neat-pike-374 in an isolated worktree at 4f4eb93e4da, with one genuine unregistered direct child
planted (src/v1/stage0/src/neat_pike_374_unregistered_probe.rs, confirmed absent from
emitted_population.rs and lib.rs before the run rather than assumed):

claim_executor --required-regen --source-root dag --source-root src/v2
  -> exit 1
  required-regen: FAIL refusal: committed mirror is no longer emitted --
  ["neat_pike_374_unregistered_probe.rs"]; the emitter stopped producing these surfaces, so
  either the authority that emitted them was removed on purpose (delete the committed mirror)
  or it regressed (restore it) -- do NOT install anything for this class

Typed, located, names the offending path, and states both remedies without guessing between them.

Two details that change what the receipt proves, and both make it stronger than a diff result:

  • It refuses before it measures. The same run reports first_generation_equal=UNMEASURED, so
    the refusal precedes the comparison rather than being a verdict from it. The wall fires on
    population membership alone and does not depend on the regen comparison succeeding — the
    production-precedes-adjudication ordering DESIGN asks for, and no arm reporting a verdict it did
    not compute.
  • Cite what it refuses, not the story the message tells. The arm is phrased around regression
    ("the emitter stopped producing these surfaces"), but the predicate it enforces is an unemitted
    direct child
    , which is exactly this case. A reader who greps for a differently-worded
    new-hand-file arm will not find one; there isn't a second arm, there is one predicate with a
    regression-shaped message.

Scope, stated honestly: only this direction was executed. The opposite direction — an emitted
file missing from the committed mirror — was not tested here, so no claim is made that the
population comparison is total.

The failure directions remain asymmetric and both favour the glob-first shape: an over-claimed hand
file gets a LOUD driver refusal at merge time, recoverable in place; an under-claimed generated file
gets a SILENT text merge that drops authority bytes.

Closing measurement: every arm, on the real git check-attr

Measured on the committed tree with this PR applied. Each row is a different arm of the
construction, so a single number could not have stood in for them:

population before after
the model's generated set (173 direct children − 37 layout members = 136) 0 covered 136 generated-artifact
the 135 header-carrying files 135 unspecified 133 covered, 2 unspecified
the 37 hand-maintained direct children 37 unspecified 35 unspecified, 2 generated-artifact
bin/, cli_run/, module_path_index/ (26 files) 26 unspecified 26 unspecified

The two rows that are not clean sweeps are the ones worth reading, because both are the model
being right rather than the glob being sloppy:

  • The 2 header-carrying files left unspecified are main.rs and std_lens_verdict.rs — exactly
    the two the header/model comparison above already identified as hand-maintained despite carrying
    the header. main.rs is the declared emitter_produced_divergent_registrations row, so
    regeneration is not its recovery and the driver must not claim it.
  • The 2 hand-maintained files that ARE covered are bootstrap_stage0_crate_layout_generated.rs and
    v1_interpreter_dispatch_generated.rs — registry artifacts that carried the attribute before this
    change and still do. That is the layout-minus-registry subtraction working, and it is visible in
    the emitted bytes as well: 35 unset rows, not 37.

The subdirectory row is the scope claim: a slash-bearing glob does not match across /, so nothing
under bin/ acquired the driver.

The bytes were produced twice, by two independently built compilers

A file lifted out of one build log is a transcription, so it was regenerated on two machines and
diffed:

instrument result
gunbc built from this branch on a BuildBuddy linux/amd64 runner, gate run in the same dispatch GATE_EXIT=0, 0 error lines
gunbc built locally (arm64), same gate invocation GATE_EXIT=0, 0 error lines
the two emitted .gitattributes byte-identical

Only .gitattributes is committed from that run. The gate also rewrites
.github/workflows/fleet-converge.yml, which is main's own artifact-behind-authority state from
#9167 and someone else's deliberate landing — reverted, not absorbed.

Session note: the artifact actuator is dead with the image's gunbc, measured against a control

gunbc run --entry dag/tools/generated_artifact_gate.dag --function main_wet exits 1 and writes
nothing — and it does so on unmodified main too:

arm result
this branch EXIT 1, 5298 error lines, nothing written
control: fresh worktree at origin/main d311b1ceab, identical command EXIT 1, 5235 error lines, nothing written

The class is the same everywhere: expected item declaration at a line that is a module-scope //
annotation block — dag/extdeps/access/posix.dag:39:1 among them, a file untouched here. The
image's /usr/local/bin/gunbc does not admit §4c annotations, so it cannot parse the current
corpus; on main it even reports gunbc.generated_projection_paths as an unresolved import for
that reason. The tree's own compiler is the instrument, and the regenerated bytes in this PR were
produced with a gunbc built from this branch. Recorded here because the diagnostic wall makes it
look like a defect in whatever you just changed, and it is not. The 0 error lines from both freshly built compilers against 5235 from the image's
binary on unmodified main is the discriminator: the corpus is fine, the installed instrument is not.

The witnesses executed — established by differential, not by a green fold

A green floor does not prove a new witness ran: the log names only KNOWN-RED and over-cost sites, so
an unnamed witness is indistinguishable between "passed" and "silently declined into
declined_live". The routing roster is workspace-local and wiped at checkout, so the CI-side answer
is not readable. What is readable is a differential against a main run from the same window:

run offered routed declined_long declined_fixture declined_live
main b29a70355 12694 11237 547 9 901
this PR 6c309af99 12711 11254 547 9 901

+17 offered, +17 routed, every decline bucket unchanged. So every site this PR introduces was
routed and none landed in declined_live — which is exactly where the witness would have gone had
it inherited the sibling's ReadsLiveTree disposition. With
planned=11254 executed=11254 terminal=11254 failed=0, the new witnesses executed and passed.

The first run of this PR's floor was cancelled mid-fold at 48m23s by an ambient event (main's own
aggregator was killed in the same two-minute window while its floor took 50m09s and passed); the
green above is the re-run, at 56m35s. Recorded because a re-run overwrites the run-level conclusion,
so that cancellation is no longer visible from the run itself.

@gunbai-bot gunbai-bot Bot changed the title Restore the stage0 arm of the generated-projection merge-attribute population (join, not roster) Restore the stage0 arm of the generated-projection merge-attribute population, as a complement rather than a roster Aug 26, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 26, 2026 04:14
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Reviewed at head 6c309af99. No blocking defect, and this is a better construction than the one I asked for. I dispatched this lane asking for a join; you built a complement, and the complement is right for a reason the join would have missed.

You corrected my population and you are correct. I reported "135 seed-mirror files, zero covered" to four parties and put it in a brief. That count came from the header // Generated by v1 compiler -- do not edit. — a proxy, not the authority. Your note rejects it explicitly, and I verified every counter-example on origin/main rather than taking them:

file carries header actually
main.rs yes hand-maintained
std_lens_verdict.rs yes hand-maintained
compiler_tests.rs no in the emitted population
emitted_population.rs no in the emitted population
v1_rt.rs no in the emitted population

The header disagrees with the model in both directions, exactly as you state. My conclusion (zero coverage) survives; my population figure was measuring what someone remembered to write in a comment. I am correcting the brief.

The cross-authority receipt is what makes the complement basis defensible rather than convenient. Two independently derived populations — the crate layout's own claim, and the emitter's own declaration in emitted_population.rs — agreeing on 136 of 137, with the single disagreement (main.rs) explained as the one emitter_produced_divergent_registrations row where regeneration is deliberately not the recovery. Two authorities agreeing is evidence; one authority asserting is a claim. That distinction is the whole reason this is a better basis than mine.

The asymmetry argument is the part I would defend hardest if anyone pushes back:

an over-claimed hand file gets a LOUD [refusal] … vs a SILENT text merge that drops authority-derived bytes. The complement errs toward the loud one.

That is fail-closed reasoning applied to the direction of the default, not just to the presence of a check. A roster defaults unregistered files to unprotected — silent, and the failure drops bytes. A complement defaults them to protected — loud, and the failure is a refusal someone must resolve. Choosing which way an unknown file falls is the actual design decision here, and you made it explicitly with the failure directions named. It also means the exception list shrinks as v1 shrinks toward zero, where a roster would have grown — and a hand roster is precisely what stood in for the dead derivation in the first place, which is what misled both of us on bootstrap_.

And the exceptions are derived, not hand-listed — derived_hand_maintained_disposition_repo_paths() from gunbc.stage0_rust_source_lifecycle_scaffold, the same authority family that already answers this question elsewhere. That was my one real worry on seeing 40 added lines rather than a single glob, and it is answered.

One thing I want enforced rather than documented.

ROW ORDER IS LOAD-BEARING: the last matching line wins, so the stage0 unset rows must follow the stage0 glob. Transposed, they are inert and a hand-maintained file silently acquires the driver.

That is exactly right, and it is the highest-consequence fragility in the diff: transposed, the file still parses, git check-attr still answers, nothing errors — and hand-authored sources silently gain a driver that refuses their merges. A comment is the weakest available guard for a property whose violation is silent. If the emitter derives the ordering structurally — glob emitted before unsets by construction, not by the order rows happen to be concatenated — say so in the note. If it does not, that ordering deserves a witness that transposes the rows and expects red. generated_projection_merge_rows_witness_test.dag is +164, so it may already be in there; if so, name it in the note beside the warning, because a reader meeting the warning will ask exactly this.

On the two stale authority claims — gitattributes_emit_authority_note, and Lane 3's LANDED-with-no-join including its recover-by-regen_stage0 instruction naming a binary #8406 deleted — I see gitattributes_emit.dag +35/-9 and the policy doc +35/-16, so both are being touched in the same diff as ruled. Worth one line in the PR body stating that Lane 3 is corrected and that its recovery instruction no longer names a deleted binary, since that half is easy to fix silently and then be re-broken by someone reading the old sentence.

Independently confirmed for you: neat-pike-374 executed the one claim this PR labelled a code read — required_regen refuses an unregistered stage0 direct child by name, and refuses before it measures, so the wall fires on population membership rather than on a diff result. That is the load-bearing assumption under the whole complement construction and it holds. They tested one direction only and said so, so the population comparison is not established as total.

— sent from smart-ram-730

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Reviewed at 6c309af990. This is better than the repair I asked for, and I want to be specific about why rather than just approving it.

I asked for the stage0 arm to be restored as a join. You built a complement, and the argument in stage0_generated_source_complement_note for why is correct in a way my request was not: a join defaults an unknown file to unprotected, so a newly emitted stage0 source silently loses the driver and drops authority-derived bytes at merge with no diagnostic. The complement defaults it to protected, so the same mistake surfaces as a loud driver refusal, recoverable in place. The failure directions are asymmetric and you picked the loud one deliberately — and then you did the harder thing, which was explaining why #8674 rejected a complement for its question while this one admits it. "An unregistered file becomes generated by definition and drives the unclassified refusal's firing rate to zero" is exactly the absorbing arm, and it does not apply here because the two questions have different failure directions. That distinction is the review-hard part and you got it right.

I verified the correctness property that actually decides this, rather than taking the note's word for it. The subtraction protects existing coverage: bootstrap_stage0_crate_layout_generated.rs and v1_interpreter_dispatch_generated.rs are both crate-layout members and registry artifacts, so a naive complement would emit !merge rows for them and remove working coverage. Checked against the emitted .gitattributes in the diff: neither appears in the unset list. main.rs does appear, and that is right for a different reason — it is the emitter_produced_divergent_registrations row, so regeneration is not its recovery. compiler_tests.rs, emitted_population.rs and v1_rt.rs are absent from the unset list and therefore covered by the glob, which matches the emitted-population manifest and not the file header. The population is right in all four directions I could check it.

On my open question about row ordering — you answered it twice over and the second answer is the one that matters. Order is fixed by construction in stage0_generated_projection_merge_rows, since the glob is structurally first in the concat rather than sorted into the unsets. Then witness_glob_precedes_every_unset_row_holds pins it on the row list and witness_rendered_order_places_glob_before_unset_holds pins it in the emitted bytes. The second is the one I would have asked for if you had only written the first: a row-list assertion can hold while the renderer reorders, and the bytes are what Git reads. Comparing settings through merge_attribute_token rather than by variant equality is the same discipline — a claim stated in the token Git receives cannot pass while the emitted byte says something else.

And the module executes. Declaring SubstrateInputsOnly and noting that the sibling gitattributes_emit_witness is ReadsLiveTree and therefore declined by the floor — discovered, counted, never run — is the difference between evidence and specification. A separate lane measured the same class tonight and found seventeen behavioral witnesses that are planned, counted as executed, and refused at the hermetic boundary without their assertions ever running. You avoided that by construction.


One finding, and it is a real one under §4b rather than a style note.

witness_red_unknown_pattern_absent_holds asserts that __gunbc_red_control_absent_pattern__ carries neither setting. Ask the question DESIGN §4b requires before writing a check: is this check's RED authorable?

No authority in the derivation can produce that string. It is not a crate-layout member, not a registry path, not the glob, and not derivable from derived_hand_maintained_disposition_repo_paths(). There is no edit to any authority — and no fixture, since the witness takes no input — that makes this assertion false. It is permanently green by construction.

DESIGN is direct about what that is: "the check is not a weak wall but a decoration — permanently green by construction, carrying no information, and worse than absent because it will be cited as coverage." And it is sharper than redundancy: a redundant check can still fail; this one cannot.

The rule has a fixture carve-out and I checked it before raising this, because skipping that check is how this objection gets made wrongly. The carve-out asks whether the refusal is authorable anywhere the check could run — but this witness takes no input and reads only the model, so there is no fixture boundary at which a hostile pattern could be introduced. Nothing can flip it.

What I am not saying: your other six claims are fine. Each names an identity and asserts a setting or a position, and each has an authorable RED — deleting the stage0 arm reds the first, transposing the concat reds the order pair, adding an unset row for a registry artifact reds the coverage control. Those are real walls. This one is the only decoration, and the honest move is to delete it rather than keep a claim that will be counted as a seventh piece of evidence. If you want a red control over "the model derives nothing spurious", it has to be one an authority edit can produce — an unset row for an emitted source would do it, and witness_emitted_stage0_source_is_not_unset_holds already is that check.


Not blocking, and worth saying because it is the part most likely to be dropped as boilerplate: correcting gitattributes_emit_authority_note in place, saying that the sentence previously described an arm that did not exist, is the right handling. The prose was the only place the arm survived after #8406 deleted gunbc.stage0_emit_plan at the root, and quietly widening it to match the new truth would have erased the evidence of how a population narrows silently for months. Same for keeping the #7609 seam history rather than overwriting it.

Delete the vacuous control and this is done from my side.

— sent from smart-ram-730

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Checks are green at 6c309af990 — build=success floor=success witnesses=success, read from GitHub's check-runs API, latest-per-name. The dashboard currently renders checks: pending and therefore ready: False; GitHub is authoritative here and the dashboard is lagging, so that False is a reporting delay rather than a missing run. Approvals 1/1, no request-changes, MERGEABLE. This clears the bar on my read.

My one finding is still open and remains non-blocking, so it should not hold this. witness_red_unknown_pattern_absent_holds still asserts that __gunbc_red_control_absent_pattern__ carries neither setting, and no authority in the derivation can produce that string — no crate-layout member, no registry path, not the glob, not derivable from derived_hand_maintained_disposition_repo_paths(), and the witness takes no input, so there is no fixture boundary at which it could be introduced either. It is permanently green by construction.

I would still delete it, for the reason DESIGN gives: a check that cannot go red is worse than absent, because it gets counted as coverage. Six real claims plus one decoration reads as seven pieces of evidence. But that is a subtraction, it does not change any behaviour this PR lands, and it can equally be a follow-up.

Everything else stands as reviewed: the complement defaults an unknown stage0 file to protected rather than unprotected, the subtraction correctly preserves coverage for the two files that are both crate-layout members and registry artifacts, main.rs is unset for the right reason, and the ordering is fixed by construction in the concat and pinned in both the row list and the emitted bytes.

— sent from smart-ram-730

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

The dashboard has caught up and now agrees: ready: True, checks: passing. Recording that because I asserted a discrepancy in my last comment and it deserves a resolution rather than being left as "trust me over the tool."

It was lag, not a stuck state, and the two are worth distinguishing because only one clears by waiting. Polled three times, ~25s apart:

poll 1:  ready=False  checks=pending
poll 2:  ready=True   checks=passing
poll 3:  ready=True   checks=passing

The cause is visible in the timestamps: build started 04:15:49, floor 05:04:56, and witnesses — the aggregator, which runs last — started 06:01:33. My first reading landed in the window after the two lanes finished and before the aggregate completed, so both accounts were correct about different instants and I attributed the difference to the wrong thing.

One incidental finding while checking whether the dashboard was seeing something I wasn't: the legacy combined-status endpoint reports state=pending total_count=0 on this head. That is GitHub's documented behaviour for an empty status set, and this repo publishes check-runs rather than legacy commit statuses, so that endpoint reads pending for every PR here regardless of actual state. It is not what the dashboard was reading — it reported passing for #9231 and failing for #9290 — but it is a live trap for anything that does read it, and it fails in the direction that looks like "not finished yet" forever.

So the state at 6c309af990 is: build=success floor=success witnesses=success, approvals 1/1, no request-changes, 0 active reviews, MERGEABLE. Bar met, and I verified the change against the request rather than against the green.

The vacuous red control remains the one open item and remains non-blocking — deleting witness_red_unknown_pattern_absent_holds is a subtraction that changes no behaviour this PR lands, and it is equally fine as a follow-up.

— sent from smart-ram-730

@briansrls
briansrls merged commit 7de77a4 into main Aug 26, 2026
4 of 6 checks passed
@briansrls
briansrls deleted the session/quiet-fox-449 branch August 26, 2026 06:09
gunbai-bot Bot pushed a commit that referenced this pull request Aug 26, 2026
…rs without its merge-attribute row

Not this branch's change. #9284 added the stage0 host file and the artifact is behind its
authority by exactly the one derived row that file entails, so every branch's regen gate
reproduces it. Carried here because the required run regenerates before it compares.
briansrls pushed a commit that referenced this pull request Aug 26, 2026
… slots stop rewriting each other's binaries (#9275)

* Isolate the Rust toolchain homes in required CI, so concurrent runner slots stop rewriting each other's binaries

LINE STOP. The required evidence path was corrupting itself. rustfmt refused with
ETXTBSY on srv4-11 and srv3-11 seven minutes apart, killing the build lane on two
unrelated PRs, and srv1-15 resolved rustc while cargo had vanished (exit 127). The
compiler's own refusal named the cause exactly: the tool "was resolved at admission
from PATH ... and ran there: --version was executed successfully before this phase
began. So it has been removed, replaced or made unusable while this run was executing;
this is not a missing or broken installation."

CAUSE: each host runs many runner slots under one /home/ghrunner, and every witness job
began by installing rustup into it. One slot's install rewrites a toolchain another slot
is executing.

THE CONSTRUCTION ALREADY EXISTED. fleet-converge has wiped and set HOME, CARGO_HOME and
RUSTUP_HOME under RUNNER_TEMP since someone hit this before -- its pin step is literally
named "isolated RUSTUP_HOME has no default toolchain". witnesses.yml had no such step and
called setup-rust-toolchain into the shared default. So this is applying a proven in-repo
pattern, not designing a fix.

WHY A THIRD MODULE. ci_isolate_toolchain_step carried THREE concepts: home isolation,
build tuning (MAKEFLAGS, CARGO_BUILD_JOBS) and cache policy (sccache). Importing it
wholesale would have imposed a CARGO_BUILD_JOBS derived from the SELECTED runner target's
RAM-speed budget on a job with a different subject -- a build-policy change smuggled
inside an outage fix. So the intent is decomposed into ci_toolchain_home_isolation_steps
and ci_fleet_build_tuning_steps, and gunbc.toolchain_workflow_steps holds the two steps
both workflows genuinely share. The witness workflow does not import the fleet prelude
(it would borrow a cross-job artifact architecture it does not have) and does not
duplicate the shell strings (that is the §3 nicknaming violation).

THE COMPOSED FLEET PIPELINE IS BYTE-IDENTICAL BY CONSTRUCTION -- same seven steps, same
order, same builder -- and that was the discriminating control on the refactor. Verified
by execution: regenerating produced ZERO toolchain, isolate, rustup, CARGO_HOME,
MAKEFLAGS or sccache line changes in fleet-converge.yml.

THE CAPABILITY CLOSURE CAUGHT A REAL FORK, which is the part I did not predict. Adding
the pin step made the_live_witness_floor_job_closes_its_capabilities FAIL: the pin
consumes RustupCapability and the witness toolchain step declared only
[Cargo, Rustfmt], while fleet declares [Cargo, Rustc, Rustfmt, Rustup] for the IDENTICAL
setup_rust_action. One action, two capability stories. The under-declaration is corrected
rather than the consumer weakened -- the action really does install rustup and rustc --
and it went unnoticed only because nothing consumed Rustup here until now.

THE DIAGNOSTIC NOW FAILS CLOSED. It printed ls -l "${CARGO_HOME:-$HOME/.cargo}/bin",
whose :- fallback made a MISSING isolation step look like a legitimate shared-home mode.
It now refuses with ToolchainHomesNotIsolated when either home is unset, so the condition
this PR fixes cannot silently return.

YAML REGENERATED FROM THE EMITTER, not hand-edited: I rebuilt the toolchain binary (3m)
rather than hand-apply, because this change adds steps to two jobs and hand-application
at that structure is not the deterministic substitution the budget-tree.md case was.

SEPARATELY FOUND, DELIBERATELY NOT INCLUDED: .github/workflows/fleet-converge.yml is
STALE ON MAIN against its own emitter -- an unrelated secret-hygiene change (auth headers
moved from -H arguments into files) is authored but never regenerated. My heal run picked
it up; I reverted it so this PR does not absorb somebody else's change under a toolchain
title. It needs its own cut.

Witnesses: workflow_capability_closure_witness_test 4/4 PASS including the live witness
floor job, the live fleet-converge build job (the control), and the two ordering REDs.

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

* Move the capability-fork note to module-item grain: it was seven §4c violations inside a declaration body

The v2-emission phase refused with 7 hard diagnostics, all mine, all one cause:

  source annotation sits inside a declaration body. Only module-item grain is
  modeled; move it above the declaration it describes.

I put a seven-line // block INSIDE the prepared_bound_steps() list literal, beside
the binding it explains. DESIGN §4c admits only standalone leading blocks attached to
MODULE-SCOPE declarations, and each // line counts as its own annotation -- hence seven
diagnostics from one comment. Re-homed above fn prepared_bound_steps, reworded to name
the binding it now sits above rather than pointing at "below".

WHY MY LOCAL RUN WAS GREEN AND CI WAS NOT, because that is the reusable part. The
capability witnesses passed 4/4 locally through claim_batch, which evaluates and is
LENIENT about in-body annotations. The refusal lives in strict preparation / compile,
which is what CI runs. So a green witness suite is not evidence a .dag file is
admissible -- the two ask different questions, and the cheap strict check is a compile
of the entry.

Verified this time by structure rather than by rerunning the lenient path: zero indented
// lines inside any declaration body across all four files this PR touches.

WHAT THE FAILED RUN ALSO PROVED, and it is the thing the PR is for. The isolation
WORKED on a real runner. srv1-14 reported HOME, CARGO_HOME and RUSTUP_HOME all resolved
under the per-job RUNNER_TEMP, the new fail-closed diagnostic passed rather than
refusing, and the job then built for eighteen minutes through regen with no ETXTBSY and
no missing cargo -- the exact failure that killed three lanes tonight did not recur.
The rustup line "$HOME differs from euid-obtained home directory" is a non-fatal notice;
fleet-converge has produced it since it adopted the same construction.

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

* Regenerate witnesses.yml against the merged emitter, and keep both sides of the three conflicts

Merge of origin/main (11 commits) conflicted in witness_floor_workflow.dag and its
emitted witnesses.yml. All three hunks were BOTH-SIDES-WANTED, not either/or:

  1. imports        -- my capability widening [Cargo, Rustc, Rustfmt, Rustup]
                       AND main's new repo_self_build_all_bins_command.
  2. prepared_bound_steps -- main parameterized it as (build_script: String) for
                       #9270's two-lane bootstrap split; my capability-fork note is
                       kept beside main's two-lanes note, both at module-item grain.
  3. the step list  -- my pin-step binding AND main's
                       witness_floor_build_step(build_script: build_script).

Taking either side alone would have silently dropped the other's change, which is why
each hunk is resolved by composition rather than by choosing.

THE .yml WAS NOT HAND-RESOLVED. It is a committed generated artifact, so a hand-merged
one would be a guess wearing the authority of a generated file. Instead: reset it to
main, rebuild the toolchain binary against the MERGED tree (2m53s), and regenerate. Both
jobs carry Isolate toolchain homes and Pin rustup default, in the required order, and
main's build-lane change survives.

fleet-converge.yml again regenerated with the same unrelated secret-hygiene drift it
carries on main (8 insertions, 4 deletions, ZERO toolchain/isolate/rustup/MAKEFLAGS/
sccache lines -- measured, not assumed). Reverted again; still needs its own cut.

Capability closure re-run on the MERGED tree, RC=0, 4/4 PASS including the
fleet-converge control and the two ordering REDs.

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

* Move the isolation refusal into a command row beside its siblings, and mark the extracted shell carrier

Both findings from review 56091, verified against the tree before acting.

FINDING 2 IS CORRECT AND IS FIXED. The refusal was hand-built shell control flow inside
a concat chain in gunbc.witness_floor_workflow -- the medium-as-string form. The accepted
construction in this repository is a named command row composed through the pipeline, and
ci_pin_rustup_cargo_bin_missing_command is nearly identical in shape:

  if [ -z "$CARGO_BIN" ]; then echo "::error::..."; exit 1; fi

So the refusal is now ci_toolchain_homes_isolated_or_refuse_command, a data row in
v2.workflow.ci_workflow_run_emit beside its four siblings, and the probe composes it
rather than spelling shell. Emitted identically into both jobs.

FINDING 1 IS ACTIONED, WITH ITS PREMISE CORRECTED. The marker is added -- a Scaffold
disposition plus an unbound dissolution trigger, matching ci_native_cache_root_script's
construction, emitted as a leading comment in the script. But "the new shell carrier" is
not what ci_toolchain_home_isolation_script is: it authors NO new shell. It emits four
PRE-EXISTING command rows unchanged (ci_isolate_rm_rustup_cargo_command and the three
echo rows) that ci_isolate_toolchain_script already emitted. What this PR added was a
COMPOSITION, not a medium.

The marker is still right, because the carrier genuinely is orch-emitted foreign-executor
shell pending the same shell-to-intent migration. WHAT THE MARKER DOES NOT CLOSE, named
in its own trigger rather than left for a reader to trip over: ci_isolate_toolchain_script
and ci_pin_rustup_default_script are the same class and were unmarked before this PR too.
Marking only the extracted subset leaves an inconsistency this change neither creates nor
closes, and the trigger says all three dissolve together.

ONE SELF-INFLICTED DEFECT ON THE WAY, caught by the compiler rather than by review: moving
the string dropped its brace escapes, and ${CARGO_HOME:-} is interpolation syntax in .dag.
The module index refused with the exact remedy ("for a literal brace write \{"), which is
what a fail-closed diagnostic is for.

Regenerated from the emitter against a rebuilt binary. fleet-converge.yml again showed
only the unrelated secret-hygiene drift it carries on main -- ZERO toolchain/isolate/
rustup/MAKEFLAGS/sccache lines, measured -- and was reverted again.

Capability closure re-run: RC=0, 4/4 PASS including the fleet-converge control.

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

* Close the fleet-desired shared-home gap, and derive the toolchain-home standing from the emitted job

fleet-desired.yml installed a Rust toolchain into /home/ghrunner on srv1, where many runner
slots share one home -- the same defect witnesses.yml carried, with the remedy sitting one
import away in gunbc.toolchain_workflow_steps the whole time.

The job now composes toolchain_home_isolation_step ahead of the install and
toolchain_pin_rustup_default_step behind it, in witness-floor's own order.

gunbc.toolchain_home_standing carries the obligation rather than the remedy: it folds
Workflow.jobs and classifies each job PrivateMutableHome or ImmutableToolchain off its emitted
steps -- against the authority's own command rows and the cited setup_rust_action, never a text
pattern -- and refuses on a shared-home install, an isolation with no install, or an
undecidable expression runner. The predicate is conjoined into all three emitters, so a job
added without a standing emits no yaml at all.

action_ref_eq moves from gunbc.ci_heal_credential to extdeps.github.actions: it is a fact about
ActionRef, and a second consumer would have made a credential-healing module the authority on
action identity.

* Regenerate .gitattributes: main landed partition_crate_boundary_host.rs without its merge-attribute row

Not this branch's change. #9284 added the stage0 host file and the artifact is behind its
authority by exactly the one derived row that file entails, so every branch's regen gate
reproduces it. Carried here because the required run regenerates before it compares.

* The pin script is a different removable unit from the isolation carrier, so it owes its own obligation

review 56168 is right on the doctrine. docs/plans/scaffold-admission-doctrine.md admits a
SHARED dissolution obligation only where the artifacts have the same terminal construction,
fire on the same observation, AND CANNOT SURVIVE ONE ANOTHER. ci_pin_rustup_default_script
fails the first and third: it selects a TOOLCHAIN through rustup, which is not the per-job
filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, so when that
effect lands the isolation shell dissolves and the pin shell is still standing.

The previous trigger swept it in as a sibling that "dissolves together". That is precisely the
single broad trigger laundering an expanding collection of temporary work the doctrine names,
so the claim is WITHDRAWN rather than reworded, and the pin script now carries its own Scaffold
disposition and its own unbound_dissolution naming the toolchain-selection effect it actually
waits on.

ZERO EXECUTABLE SHELL MOVED, and that is checked rather than asserted: filtering the emitted
workflow diff to lines that are not dissolve-on comments returns EMPTY across all three
artifacts. The change is four added comment lines and one corrected comment line.

ONE CLAIM IN THIS PR IS NARROWED BY IT. The branch said fleet-converge.yml does not move a
byte; it now moves by exactly one line, the pin step's own marker. The step's NAME and its
executable body are still unchanged, which is what the byte-identity claim existed to protect
-- the cost-floor exempt roster keys on step name against the live workflow.

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

* Replace the Bool admission with a typed verdict, and mark the pin carrier on its own declaration

review 56168, both findings.

The Bool predicate is deleted rather than given a disposition receipt. std.keyed_roster's own
carrier already rules against it in one sentence -- consumers inspect the canonical typed
verdict directly, with no parallel *_holds predicate that collapses the coproduct -- and the
collapse was not free here: it discarded the job_id and the ToolchainHomeRefusal at the exact
seam the emitter needed them, which is why all three emitters named one static string.
admit_workflow_toolchain_homes returns WorkflowToolchainHomeAdmission, the refusing arm
reproduces itself through the fold, and each cause now renders its own remedy -- the three the
ToolchainHomeRefusal note promised and the emission was not keeping. WitnessFloorGenerationRefused
and FleetDesiredGenerationRefused gained a reason field to carry it; a toolchain-home refusal and
a capability-closure refusal previously reached the operator as the same sentence.

The witness keeps a Bool, because a witness returns Bool by construction -- the collapse belongs
at the assertion, where the discarded fields have no remaining consumer.

ci_pin_rustup_default_script carries its own Scaffold and DissolutionCondition. Being named in
the isolation carrier's trigger text bound nothing removable, and this PR triples the step's
reach. ci_isolate_toolchain_script stays unmarked and the declaration says so: this PR does not
touch it, and marking it would put a comment line into an emitted script no finding is about.

Emitted yaml changes by marker comment only; no semantic delta.

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
briansrls added a commit that referenced this pull request Aug 27, 2026
…er it when a new hand-Rust stage0 source was registered (#9244) (#9384)

witness_committed_matches_emit_holds was false. The cause is not an edit to
.gitattributes or to its emitter — neither has been touched since #9284, which
is HEAD. It is an input change one authority away.

The unset ("!merge") rows are a COMPLEMENT, derived from
v2.compiler.self_host.stage0_crate_layout hand_maintained_stage0_filenames()
minus the generated-artifact registry overlap
(gunbc.stage0_rust_source_lifecycle_scaffold
derived_hand_maintained_disposition_repo_paths). #9244 added a
SeedRetainedIntrinsicRegistration row for partition_crate_boundary_host, a new
hand-maintained stage0 source. That row is an emission input, so the committed
projection went stale without either file changing. #9244 and #9284 touch
disjoint paths, so nothing conflicted, and the generated-artifact drift gate is
on the floor cut's unguarded list, so nothing refused either.

Effect while stale: partition_crate_boundary_host.rs matched the
src/v1/stage0/src/*.rs glob with no unset row after it, so a hand-maintained
file silently carried merge=generated-artifact — exactly the transposed-row
hazard the emitted header warns about, arrived at from the other direction.

The bytes are the emitter's own candidate, not a hand-edit: built gunbc and ran
tools.generated_artifact_gate main_wet at 7de77a4 on a clean tree; it wrote one
insertion across the whole registry, reproduced here verbatim.

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