Repository navigation
The behavioral receipt asked one generator for a mirror two generators write: ask the population that owns the path - #8753
Merged
Conversation
…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 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/srcis 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: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:
The differential compiles a driver that calls the authority's declared functions against the mirror.
v1_interpreter_dispatch_generated.rsexposes enums andlookup_*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:
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.rsandbootstrap_stage0_crate_layout_generated.rshave 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.dagis deleted (cli_run.rsstill 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
generated_artifact_body_for_pathfoldscommitted_generated_artifacts()(the rostermain_wetfolds), filters by the existingartifact_path, and dispatches through the existingartifact_generate. No new roster, no forked dispatch — which was the available shortcut and is not needed.GeneratedArtifactPathNotGeneratedis a positive answer that routes to the mirror-emit population. Folding it intoRefusedwould tell a caller holding an ordinary mirror that generation failed for it — false, and differently actionable.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.NotGeneratedwould route a real generated artifact to the mirror emit and refuse it for the wrong reason.Evidence by execution
Same worktree, one variable changed between runs:
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 claimARTIFACT-DRIFT — ... does not match what its authority generates. Regenerate it (main_wet on dag/tools/generated_artifact_gate.dag) and commit the resultreceipt-selftest OK both arms discriminate; the fourDriverTranscriptunit tests still passThe 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=1with 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:It recovered green on rerun of the pinned head (
63704a41):phases_run=8 failed=0,floor
planned=9931 executed=9931 failed=0,passed9623 → 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:
ETXTBSYappears nowhere indag/src/v2, and one occurrencewhose writer cannot be named does not justify a carrier that would claim more than the evidence.
srv1-04(fromattempts/1; the rerun overwrites the job record). Four co-tenant slotssrv1-01/02/03/05were live at 12:31:43Z. Runner names aresrvN-MM= slot MM on host srvN, so these share one machine.Install Rust toolchainstep 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.srv1-05was insideBuild the witness fold12:30:27→12:33:16 — a concurrentcargo buildon the same host. Writesregistry/and.package-cache, not the exec'd binary.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.rsresolve_host_tool_programresolvesargv[0]by first match overPATH,then
$CARGO_HOME/bin, then$HOME/.cargo/bin, andCommand::newexecs that resolved path —but both failure sites format
argv[0], the bare spelling: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/cargofrom a per-job copy — which arethree different mechanisms with three different remedies (only one of which involves co-tenancy
at all). Two sites,
emit_host_run_transportandemit_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