Skip to content

The behavioral receipt asked one generator for a mirror two generators write: ask the population that owns the path - #8753

Merged
gunbai-bot[bot] merged 3 commits into
mainfrom
session/snappy-ferret-99-causeb
Aug 21, 2026
Merged

gunbai-bot[bot] merged 3 commits into
mainfrom
session/snappy-ferret-99-causeb

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The behavioral receipt asked one generator for a mirror two generators write

This body has been rewritten. The first version claimed drift coverage as a side effect of producing the candidate. Running it disproved that, and the mechanism below is what the change actually does. Both existing approvals predate this correction.

The defect

src/v1/stage0/src is written by two generators: the v1 compiler emits module mirrors, and gunbc's artifact emitters write a handful of generated files whose headers say so. The receipt asked only the first and refused when it came back empty:

behavioral-receipt: gunbc.v1_interpreter_primitive_surface REFUSED — the emit produced no
v1_interpreter_dispatch_generated.rs, so there is no candidate to compare.
Not equivalence: a missing candidate is ignorance

That refusal is correct as written and this change does not weaken it. What was wrong is that the second population was never asked, so the refusal was unconditional: any change to the interpreter dispatch roster or the stage0 crate layout redded this phase with no reachable green short of hand-editing a generated file — the laundering pattern DESIGN's CI paragraph already closed one gate over, reached from the other direction.

What I got wrong, and how running it caught me

My first version produced the candidate and handed it to the behavioral differential. That cleared the refusal above and hit a second, deeper one:

REFUSED — seed: the driver did not compile against the mirror: error[E0425]: cannot find
function `v1_interpreter_arm_shape_derivability` in module
v1_compiler::v1_interpreter_dispatch_generated

The differential compiles a driver that calls the authority's declared functions against the mirror. v1_interpreter_dispatch_generated.rs exposes enums and lookup_* fns — it is derived from the authority's data, not a Rust projection of its functions. Those functions are not there and never will be, so the differential's precondition does not hold for this population at all.

I had asked "what is missing" without asking "what will the consumer do with it once it has it".

The mechanism

Two populations, two checks that fit their subjects:

population check why
module mirror behavioral differential the mirror answers the calls the authority declares
generated artifact byte identity there is nothing to call; the whole content is the product

For a generated artifact, byte identity is not a weaker stand-in for behavioural equivalence — it is the complete statement of correctness.

This makes the coverage claim real rather than asserted. v1_interpreter_dispatch_generated.rs and bootstrap_stage0_crate_layout_generated.rs have had no drift check anywhere since the floor cut dropped the generated-artifact drift gates (DESIGN lists them among what is unguarded). This is now the mechanism that gives them one — a climb, not only a gate repair.

I nearly went the other way: exclude such authorities at selection, on the grounds that drift coverage belonged to the drift gate. I checked, and that gate does not exist — generated_artifact_drift_test.dag is deleted (cli_run.rs still names the path twice), and the surviving witness checks only that generation succeeds, never that it matches the committed file. The exclusion would have deleted the last observer while citing a gate that wasn't there.

Design notes

  • A projection, not a second authority. generated_artifact_body_for_path folds committed_generated_artifacts() (the roster main_wet folds), filters by the existing artifact_path, and dispatches through the existing artifact_generate. No new roster, no forked dispatch — which was the available shortcut and is not needed.
  • Three states, not two. GeneratedArtifactPathNotGenerated is a positive answer that routes to the mirror-emit population. Folding it into Refused would tell a caller holding an ordinary mirror that generation failed for it — false, and differently actionable.
  • Asked by population, not by fallback. A fallback would make absence in one producer mean presence in the other, so an unknown path would be regenerated from nothing instead of refused. A path in neither still refuses, and says which.
  • ExecutionMode::Hermetic. The projection is pure; a host effect reached during it means a generator is doing something a gate must not perform on its behalf.
  • No default match arm. An unrecognised variant is ignorance; guessing NotGenerated would route a real generated artifact to the mirror emit and refuse it for the wrong reason.
  • Cost shape, fixed before shipping. The first draft resolved the whole closure inside the per-module loop — unit of computation the corpus, unit of fact one path. One resolve now, before the loop.

Evidence by execution

Same worktree, one variable changed between runs:

  • unperturbed → ARTIFACT-IDENTICAL — ... regenerates byte-for-byte from its authority. This is identity, not behavioural equivalence: the artifact exposes no function this fragment could call, so its bytes are the whole claim
  • one appended comment line, bytes confirmed to differ before the run rather than assumed → ARTIFACT-DRIFT — ... does not match what its authority generates. Regenerate it (main_wet on dag/tools/generated_artifact_gate.dag) and commit the result
  • perturbation restored afterwards, verified by byte comparison rather than inferred from the copy succeeding
  • receipt-selftest OK both arms discriminate; the four DriverTranscript unit tests still pass

The positive arm alone would prove only that the mechanism reaches the artifact. The drift arm proves it discriminates — without it the check would pass on everything, which is strictly worse than the refusal it replaces, since a refusal at least announces that nothing was verified.

Downstream

This is the last cause blocking #8726, which otherwise sits at phases_run=6 failed=1 with every other phase clean.


Appendix — ETXTBSY forensics (NOT part of this diff; recorded so it is not re-derived)

This PR's CI red once on the floor with failed=1:

FAIL v2.test.execution.emit_host_transport_wire.emit_host_transport_green_run_holds
  errored: emit_host_run_transport: spawn "cargo" failed: Text file busy (os error 26)

It recovered green on rerun of the pinned head (63704a41): phases_run=8 failed=0,
floor planned=9931 executed=9931 failed=0, passed 9623 → 9624 — same roster, exactly +1,
so the recovered subject is the witness that errored and not a smaller question.

Status: recovered, cause NOT established. A green rerun is consistent with both a host race
and an intermittently-reproducing defect, so it does not discriminate them. Recorded here rather
than filed as a typed carrier: ETXTBSY appears nowhere in dag/src/v2, and one occurrence
whose writer cannot be named does not justify a carrier that would claim more than the evidence.

precondition CONFIRMED — failing job on runner srv1-04 (from attempts/1; the rerun overwrites the job record). Four co-tenant slots srv1-01/02/03/05 were live at 12:31:43Z. Runner names are srvN-MM = slot MM on host srvN, so these share one machine.
candidate: toolchain installer REFUTED — every Install Rust toolchain step on those slots ran 1–2s (cached): 12:21:05–07, 12:28:20–22, 12:28:31–32, 12:30:26–27. None contains the instant.
concurrent activity srv1-05 was inside Build the witness fold 12:30:27→12:33:16 — a concurrent cargo build on the same host. Writes registry/ and .package-cache, not the exec'd binary.
writer of the binary UNKNOWN

Method note: concurrency requires the step's interval to contain the instant. Comparing
start times to the instant is the wrong predicate and biases toward "found none" — using it
first produced both a false absence and a candidate that the correct predicate then refuted.

The file identity is unestablished because the diagnostic cannot carry it

v1_interpreter.rs resolve_host_tool_program resolves argv[0] by first match over PATH,
then $CARGO_HOME/bin, then $HOME/.cargo/bin, and Command::new execs that resolved path —
but both failure sites format argv[0], the bare spelling:

let mut command = std::process::Command::new(resolve_host_tool_program(&argv[0])?);
...
.map_err(|e| InterpError::TypeError {
    msg: format!("emit_host_run_transport: spawn {:?} failed: {e}", argv[0]) })

So the resolved path is computed and discarded at the moment it becomes evidence, and the error
cannot distinguish a rustup shim from /opt/cargo/bin/cargo from a per-job copy — which are
three different mechanisms with three different remedies (only one of which involves co-tenancy
at all). Two sites, emit_host_run_transport and emit_host_run_transport_cached.

Not fixed here — unrelated to this PR's subject, and bundling it would mean reverting the
forensics fix to revert this one. Offered as a separate one-line repair: report the resolved
path beside the spelling. That is what makes the next occurrence self-diagnosing instead of
costing another investigation.

— sent from snappy-ferret-99

gunbc-ci-auto-heal and others added 3 commits August 21, 2026 09:45
…s write: ask the population that owns the path

`src/v1/stage0/src` is written by TWO generators. The v1 compiler emits module mirrors, and
gunbc's artifact emitters write a handful of generated files whose headers say so. The receipt
asked only the first and refused when it came back empty:

  behavioral-receipt: gunbc.v1_interpreter_primitive_surface REFUSED — the emit produced no
  v1_interpreter_dispatch_generated.rs, so there is no candidate to compare.
  Not equivalence: a missing candidate is ignorance

That refusal is CORRECT AS WRITTEN and this change does not weaken it. What was wrong is that the
second population was never asked, so the refusal was unconditional: any change to the interpreter
dispatch roster or the stage0 crate layout redded this phase with no reachable green short of
hand-editing a generated file — the laundering pattern the CI paragraph in DESIGN already closed
one gate over, arrived at from the other direction.

WHAT I ALMOST DID INSTEAD, recorded because the reasoning was wrong in a way that looked right. My
first plan was to EXCLUDE such authorities at selection, on the grounds that byte coverage belongs
to the generated-artifact drift gate. I went to verify that gate covers them. IT DOES NOT:
`dag/test/claim/generated_artifact_drift_test.dag` is deleted (cli_run.rs still names the path in
two places), the surviving witness `dispatch_emit_succeeds_on_live_roster` checks only that
generation SUCCEEDS and never compares to the committed file, and DESIGN's CI paragraph lists the
drift gates among what the floor cut left unguarded. So the exclusion would have deleted the last
observer of those artifacts while citing a gate that does not exist. Checking the convenient
argument is what caught it.

That inverts the value: this is a CLIMB, not only a gate repair. `v1_interpreter_dispatch_generated.rs`
and `bootstrap_stage0_crate_layout_generated.rs` currently have no drift coverage anywhere, and
routing them through the differential restores byte-level checking for both.

THE .dag SIDE IS A PROJECTION, NOT A SECOND AUTHORITY. `generated_artifact_body_for_path` folds
`committed_generated_artifacts()` — the roster `main_wet` itself folds — filters by the existing
`artifact_path`, and dispatches through the existing `artifact_generate`. No new roster, no second
dispatch, no edit to either authority it consults. Reaching past it to a per-artifact emitter
would have been the forked dispatch §3 forbids, and it was the available shortcut while main was
red; it is not needed.

THREE STATES, NOT TWO. `GeneratedArtifactPathNotGenerated` is a POSITIVE answer — this path is not
in the generated-artifact population — and it is what routes a caller to the mirror-emit
population. Folding it into `ArtifactGenerationRefused` would tell a caller holding an ordinary
mirror that generation FAILED for it, which is false and differently actionable: two states, one
spelling.

THE CALLER ASKS BY POPULATION, NOT BY FALLBACK. The generated-artifact population is asked first
and answers positively; only `NotGenerated` routes to the mirror emit. This is deliberately not a
fallback from a mirror-emit miss, because a fallback makes ABSENCE in one producer mean PRESENCE
in the other — a genuinely unknown path would be regenerated from nothing instead of refused. A
path in neither population still refuses, and says which.

TWO IMPLEMENTATION CHOICES WORTH NAMING
  - `ExecutionMode::Hermetic`, not Wet: the projection is pure, so a host effect reached during it
    means a generator is doing something a gate must not perform on its behalf. Hermetic refuses
    there rather than carrying it out.
  - No default match arm on the returned variant. An unrecognised shape is ignorance; guessing
    `NotGenerated` would route a real generated artifact to the mirror emit and refuse it there
    for the wrong reason.

COST SHAPE, FIXED BEFORE IT SHIPPED. The first draft resolved `generated_artifact_emit`'s whole
closure INSIDE the per-module loop — unit of computation the corpus, unit of fact one path, which
is DESIGN §6's cost-shape defect and the rule there is that a proven one is fixed regardless of the
realized n. The resolve now happens once before the loop and each path is one interpreter call
against it.

EVIDENCE BY EXECUTION, each arm confirmed to have run rather than inferred from silence:
  - generated path -> Produced, content >100 chars, exit 0
  - ordinary mirror path (std_algebra.rs) -> NotGenerated, exit 0
  - DISCRIMINATING CONTROL asserting the generated path is NotGenerated -> exit 9,
    "control fired: path IS generated". The probe can fail.
  - receipt-selftest unchanged: preserving EQUIVALENT over 12, changing DIVERGENT at the authored
    difference. Both pre-existing arms still discriminate.
  - the four DriverTranscript unit tests still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review 54327's finding. Splitting the function out of its first draft left its original summary
("Ask gunbc.generated_artifact_emit for the body...") immediately followed by the replacement
summary ("Ask the already-resolved..."), so one function carried two opening lines with no break
between them and read as a run-on that restarts mid-comment.

One block now: summary, the not-a-second-producer argument, then the cost-shape note.

Swept the rest of the diff rather than the one site named: all 18 added doc lines are two blocks,
one on GeneratedArtifactPathBody and one on the function, with no other stacking. A first pass at
detecting the class corpus-wide flagged 27 sites, every one of them an ordinary multi-sentence doc
block -- the detector was matching "period then capital letter", which is prose, not the defect.
Reported here rather than acted on, because a sweep that fixes 27 false positives is worse than
the nit it started from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…unctions to call, so check it by identity

MY OWN PREDICTION WAS WRONG AND RUNNING IT IS THE ONLY REASON I KNOW. This PR's first version
produced the candidate for a generated artifact and handed it to the behavioral differential. That
removed the "the emit produced no v1_interpreter_dispatch_generated.rs" refusal and immediately hit
a SECOND, deeper one:

  behavioral-receipt: gunbc.v1_interpreter_primitive_surface REFUSED — seed: the driver did not
  compile against the mirror: error[E0425]: cannot find function
  `v1_interpreter_arm_shape_derivability` in module v1_compiler::v1_interpreter_dispatch_generated

The differential compiles a driver that CALLS the authority's declared functions against the
mirror. `v1_interpreter_dispatch_generated.rs` exposes enums and `lookup_*` fns: it is DERIVED FROM
the authority's data, not a Rust projection of the authority's functions. Those functions are not
there and never will be, so the differential's precondition -- the mirror answers the calls the
authority declares -- does not hold for this population at all.

I had asked "what is missing" and not "what will the consumer DO with it once it has it". Two
reviews approved the version with that hole; their reasoning about the diff was sound and survives
unchanged, because what was wrong is not visible in the diff -- only in running it against the
module it exists to unblock, which neither this PR's own CI nor a reviewer does.

THE CORRECTION, WHICH IS THE MECHANISM RATHER THAN A PATCH ON IT. Two populations, two checks that
each fit their subject. A module mirror gets the behavioral differential. A generated artifact gets
BYTE IDENTITY between the freshly generated candidate and the committed file -- and for a generated
artifact that is not a weaker stand-in for behavioural equivalence, it is the COMPLETE statement of
correctness, because the whole content is the product. There is nothing to call, and nothing that
needed calling.

That also makes this PR's coverage claim real instead of asserted. The earlier body sold drift
coverage as a side effect of producing the candidate; it was not, and the body is corrected here.
`v1_interpreter_dispatch_generated.rs` and `bootstrap_stage0_crate_layout_generated.rs` have had no
drift check anywhere since the floor cut dropped the generated-artifact drift gates, and this is
now the mechanism that gives them one.

TWO NEW VERDICTS, BOTH PROVEN TO DISCRIMINATE ON ONE TREE
  ARTIFACT-IDENTICAL  the artifact regenerates byte-for-byte from its authority
  ARTIFACT-DRIFT      it does not, and the line names the regeneration command

EVIDENCE BY EXECUTION, same worktree, one variable changed between the two runs:
  - unperturbed -> ARTIFACT-IDENTICAL: "regenerates byte-for-byte from its authority. This is
    identity, not behavioural equivalence: the artifact exposes no function this fragment could
    call, so its bytes are the whole claim"
  - one appended comment line, bytes confirmed to differ BEFORE the run rather than assumed ->
    ARTIFACT-DRIFT: "does not match what its authority generates. Regenerate it (main_wet on
    dag/tools/generated_artifact_gate.dag) and commit the result"
  - perturbation restored afterwards and the restore VERIFIED by byte comparison, not inferred
    from the copy succeeding: a mutation worktree that silently keeps its mutation turns every
    later measurement into a lie.

The positive arm alone would have proved only that the mechanism REACHES the artifact. The drift
arm is what proves it discriminates -- without it this check would pass on everything, which is
strictly worse than the refusal it replaces, because a refusal at least announces that nothing was
verified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot merged commit 2eb3d20 into main Aug 21, 2026
1 of 2 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/snappy-ferret-99-causeb branch August 21, 2026 13:31
gunbai-bot Bot pushed a commit that referenced this pull request Aug 21, 2026
…r, and its finding corrects the other half too

#8753 landed the missing-candidate half from the opposite direction. Instead of
excluding a wet-actuator authority at selection, it routes the mirror to the
generated-artifact population and checks it by BYTE IDENTITY — and it rejected
exclusion explicitly, on a measurement: those artifacts have had no drift
observer anywhere since the floor cut, so excluding them would have deleted the
last one while citing a gate that does not exist.

That is right and it lands ahead of me, so `MirrorOutsideEmittedPopulation`, the
`mirror_outside_emitted_population` re-export and the exposed predicate are all
GONE rather than kept beside it. Two answers to one question is the dual
authority §3 forbids, and mine would have made #8753's new check unreachable for
exactly the population it was written for.

The same finding corrects MY arm, which the brief did not anticipate: a
zero-function authority whose mirror is a GENERATED ARTIFACT is not subjectless
either — its subject is its bytes. So selection no longer excludes on
"yields no call" alone; it asks the generated-artifact population, through the
same projection the differential loop asks (no second roster), and excludes only
on the positive `NotGenerated` answer. The context is resolved at most once per
run and lazily, so an ordinary PR pays nothing for a question only a zero-call
module raises.

Measured on today's corpus this ask changes no verdict — the one live
zero-function specimen is an ordinary v1 mirror, and it answers NotGenerated —
which is stated rather than hidden: it makes the exclusion a checked fact
instead of an assumed one, in a population the repository actively grows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 21, 2026
…s, and the regen compile half reaches ZERO hard diagnostics

Merge, not rebase: this branch is squash-merged, so curated history buys nothing
and a force-push costs everyone tracking it.

CONFLICT RESOLUTION. Three .dag authorities taken from main and recut
(generated_artifact_emit 71 qualified, 05_emit_rust 3390, trait_derive_emit 312;
0 refused, resolved_type guard 0). Two generated mirrors were NOT merged --
v1_compiler_emit_rust.rs conflicted and v1_compiler_trait_derive_emit.rs
auto-merged CLEANLY, which is the more dangerous of the two: a clean automerge of
a generated file is a hybrid of two authentic halves. Both were taken whole.

Taking MAIN's mirrors failed to compile: they reference `UnlistedImportUse` and
the import machinery this branch deleted, because main's mirrors are generated
from main's import-bearing sources. The mirror follows THIS branch's .dag, so
both were taken from the branch instead. That is a build-verified decision, not
a preference -- the seed rebuild is the discriminator, and the same rebuild is
what caught the bin/claim_executor.rs hybrid on an earlier integration.

THE REAL FIX -- an incomplete table, not a design decision. Recutting
trait_derive_emit qualified `Set<String>` to `std.types.Set<String>` at 33 sites
and produced 9 new refusals (set_contains/set_insert/empty_set "receiver must be
a Set"). The cause is that several call sites pass the FULL authored name to the
container lookups without taking the last segment (trait_derive_emit
type_expr/te sites, 04_types under_param and the receiver-arity site), so a
qualified spelling needs an explicit row -- which is exactly why
"std.types.List" already had one in five tables while std.types.Set and
std.types.Map had none. Corpus counts: std.types.List 1624, std.types.Map 745,
std.types.Set 274. The qualified spelling is the accepted one; the tables lagged
it for two of three members.

Completed four tables in std.types (arity, template algebra, alias algebra,
canonical names) for both missing names. Deliberately NOT the fifth:
`ordered_element_collections` carries List only, because Set and Map are
unordered -- adding them there would have been pattern-matching the diff rather
than reading what the table means.

std_types.rs carries the same rows as a declared BOOTSTRAP step: the tables are
consulted by the running seed, so the .dag edit alone cannot change compiler
behavior, and regen cannot yet regenerate the mirror (see below). The rows are
byte-equivalent to what the .dag authority emits and are expected to be
reproduced, not preserved, by the first successful regen.

RESULT: regen hard diagnostics 9 -> 0. The compile half is CLEAN.

WHAT NOW BLOCKS REGEN IS THE EMITTER, and this is the red I have been trying to
capture rather than a new problem:

  normalize emitted src/v1_compiler_infer.rs:
    v1_rt::rc_empty_map::<String, std.types._list<InductiveField>>()

`std.types._list` is literally to_snake("std.types.List"). That is the
qualified-type emission defect, reproduced end-to-end on a real regen artifact
instead of argued from a call site, and it confirms the located producer:
emit_container keys its container lookup on to_snake of the WHOLE dotted
address, having no declaration identity in its signature to key on instead.

Not fixed here. It belongs with the emitter work and folding it in would grow
this integration into a second change.

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.

0 participants