Skip to content

Decide a record-literal field's reference layer from identity, not spelling - #8984

Merged
briansrls merged 3 commits into
mainfrom
fix/emitter-resolved-identity-spelling
Aug 23, 2026
Merged

briansrls merged 3 commits into
mainfrom
fix/emitter-resolved-identity-spelling

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Same resolved declaration, different authored spelling, must produce the same Rust. emit_field_value_with_context decided the shared reference layer of a record-literal FIELD VALUE with set_contains(shared_types, rc_name) where rc_name is the name AS AUTHORED, while shared_types is keyed on the bare declared name — the mismatch alias_rhs_qualified_name_routing_note already records for every other lookup on that path.

A qualified spelling matched nothing, so the value was emitted UNWRAPPED into a field whose declared type is Rc<T>. That is a type error (E0308), not a style difference.

Measured, minimal, both arms against one provider

bare       uri: Rc::new(QualspellUri {
qualified  uri: QualspellUri {          <- before
qualified  uri: Rc::new(QualspellUri {  <- after

Discriminating RED, by execution

The witness returns false against an emitter with this one decision reverted, and true with it. Both runs were performed — the red is not deduced from the earlier output.

Why main is green without this

The measured seed closure contains almost no qualified type reference, so an ordinary green regen exercises the bare arm only. That is why the witness AUTHORS the qualified arm rather than relying on the corpus to contain one.

4066 qualified dotted type references already exist corpus-wide (555 files, 2314 in src/v2) and enter the seed closure as v2 self-hosting advances, so the population reaching this path grows with the roadmap rather than staying latent.

Zero-drift, structurally

qualified_last_segment is the identity on an unqualified name, so every bare spelling emits exactly as before. Receipt: required-regen over the 132-module subject reports first_generation_equal=true with only this repair mirror changed.

Declared residue — not fixed, not claimed

A qualified reference to a zero-parameter ALIAS is still peeled to its target, and where that target lives in a THIRD module the peeled name reaches output with no use-line (E0412).

Located cause: a qualified reference parses as a module-projection spine, so it fails the NoConnective-and-childless guard on the alias-preserving branch of render_rust_fn_sig_type and never reaches the alias lookup at all. Three candidate repairs were written and all three reverted rather than landed unproven, since none is pinned by an executing control.

The fixture cannot carry that RED either: compile_dag_rust_emit_check compiles against an import-driven source set, so a third fixture module turned the BARE control red — the harness refusing the closure, not the emitter failing.

Provenance

Found while driving integration/namespace-cut (#8282), where this producer accounts for 115 of the branch diagnostics. Per the agreed sequencing this lands on main first, where it is independently testable; the cut branch then takes this exact patch rather than re-inventing it.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 2 commits August 23, 2026 04:09
…elling

Same resolved declaration, different authored spelling, must produce the
same Rust. emit_field_value_with_context decided the shared reference
layer of a record-literal FIELD VALUE with

    set_contains(shared_types, rc_name)

where rc_name is the name AS AUTHORED, while shared_types is keyed on the
bare declared name -- the same mismatch alias_rhs_qualified_name_routing_note
already records for every other lookup on that path. A qualified spelling
matched nothing, so the value was emitted UNWRAPPED into a field whose
declared type is Rc<T>. That is not a style difference: it is a type error
rustc reports as E0308, and no correct declaration can compensate for it.
needs_box_wrapping reads the same authored spelling for the same decision
and is repaired with it; the witness pins the field-value arm, and that
second call is the same lookup on the same decision rather than an
independently evidenced one.

MEASURED, minimal, both arms against one provider:

  bare       uri: Rc::new(QualspellUri \{
  qualified  uri: QualspellUri \{          <- before
  qualified  uri: Rc::new(QualspellUri \{  <- after

DISCRIMINATING RED, BY EXECUTION rather than by inference: the witness
returns false against an emitter with this one decision reverted, and true
with it. Both runs were performed; the red is not deduced from the earlier
output.

WHY MAIN IS GREEN WITHOUT THIS. The measured seed closure contains almost
no qualified type reference, so an ordinary green regen exercises the bare
arm only -- which is why the witness AUTHORS the qualified arm instead of
relying on the corpus to contain one. 4066 qualified dotted type references
already exist corpus-wide (555 files, 2314 in src/v2) and enter the seed
closure as v2 self-hosting advances, so the population reaching this path
grows with the roadmap rather than staying latent.

ZERO-DRIFT, AND WHY IT IS STRUCTURAL: qualified_last_segment is the
identity on an unqualified name, so every bare spelling emits exactly as
before. Receipt: required-regen over the 132-module subject reports
first_generation_equal=true with only this repair's own mirror changed.

DECLARED RESIDUE, not fixed and not claimed. A qualified reference to a
zero-parameter ALIAS is still peeled to its target, and where that target
lives in a THIRD module the peeled name reaches the output with no
use-line (E0412). Located cause: a qualified reference parses as a
module-projection spine, so it fails the NoConnective-and-childless guard
on the alias-preserving branch of render_rust_fn_sig_type and never
reaches the alias lookup at all. Three candidate repairs were written and
all three were reverted here rather than landed unproven, because none is
pinned by an executing control. The fixture cannot carry that RED either:
compile_dag_rust_emit_check compiles the probe against an import-driven
source set, so a third fixture module turned the BARE control red -- the
harness refusing the closure, not the emitter failing. Recorded so the
next attempt does not rediscover both walls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MEASURED ON THE FIRST CI RUN, not predicted. As one conjoined claim this
witness grew RSS by 1.22GB and was the floor's worst single claim, against
0.12GB for the whole rest of the roster on the same-day main run -- both
compile_dag_rust_emit_check calls are live inside one claim and each builds
a corpus-wide scope.

The two arms are independent propositions -- the RED and its regression
control -- so they are two test fns. Each compile is now evaluated and
collected on its own, and a failure names WHICH arm broke instead of
returning one conjoined false.

DESIGN section 6, bare minimum cost: a proven cost-shape defect is fixed
regardless of the realized n. Nothing refused on this run and no budget was
exceeded, which is exactly the condition under which the fix gets skipped
and the cost is left to break someone else's budget later.

NOT FIXED HERE, because it is not mine: this PR's CI failure is
REQUIRED-FLOOR REFUSAL cause=TerminalLedgerUnrenderable
reason=seed-disposition-disagrees offending=
test.claim.accelerator_demo_execution_witness.accelerator_demo_execution_lane_witnesses,
which reproduces byte-identically on main (run 32617459831, sha dcac648).
The floor-known-red line is also identical on both -- 206 held, 6 now PASS.
Both are pre-existing main breakage, and the only line this PR moved was
the memory one.

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

The only conflict was src/v1/stage0/src/v1_compiler_emit_rust.rs, a generated
mirror. A generated artifact is not merged, it is regenerated from the authority
both sides edited: main's 05_emit_rust.dag changes and this branch's two-site
fix merged cleanly by content, so the mirror was rebuilt from that composed
authority rather than hand-reconciled.

Receipt, measured on this merge rather than transcribed: pass 1 reported
first_generation_equal=false with changed_paths=[v1_compiler_emit_rust.rs] --
exactly the staged-from-main base lacking the fix -- and pass 2 after installing
the candidate and rebuilding reported first_generation_equal=true, planned=133
executed=133, declared_divergent=1 [main.rs], the known hand-maintained entry.
The emitted delta is the two sites and nothing else: needs_box_wrapping now
compares the qualified last segment of the authored name, and
emit_field_value_with_context does the same for rc_name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 3806a26 into main Aug 23, 2026
1 check passed
@briansrls
briansrls deleted the fix/emitter-resolved-identity-spelling branch August 23, 2026 14:44
gunbai-bot Bot pushed a commit that referenced this pull request Aug 23, 2026
…overed

Operator ruling 2026-08-23: kill/quarantine it and find out why. Both halves are
here -- the move, and the measurement that makes this a filed defect rather than
a cost row.

#9031's run 32657761997 was otherwise clean (failed=0, stale_quarantine=0,
interrupted_before_verdict=0) with ONE non-pass:

  COMPLETED-OVER-COST-REQUIREMENT
  qualified_spelling_takes_the_shared_layer   wall_ms=57337 cpu_ms=57193
  [floor-claim-memory] grew rss by 1.22GB (to 11.70GB)

11x the 5000ms executor fail-stop. The fail-stop is doing its job: at 11.70GB
this claim genuinely threatens the process it runs in.

THE SPLIT THAT WAS SUPPOSED TO FIX THIS DID NOTHING, and that is the finding.
#8984's own note records the conjoined two-arm claim at 1.22GB RSS growth
against 0.12GB for the whole rest of the roster, and splits the arms into
separate test fns on the reasoning that two live compile_dag_rust_emit_check
calls sat inside one claim. Measured after the split, the QUALIFIED ARM ALONE
grows 1.22GB -- the identical figure -- and the bare arm appears in no over-cost
or memory line at all. So the entire cost was always the qualified arm. It is
not two compiles; it is the qualified-name resolution path specifically, which
is the path #8984 exists to repair.

WHY QUARANTINE AND NOT A COST ENVELOPE. An envelope large enough to admit 57s
and 1.22GB would admit the defect rather than measure it. The dissolution
condition on this row is therefore the REPAIR, explicitly not an envelope, so
nobody closes it by raising the ceiling.

WHY IT REACHED MAIN UNSEEN. #8984 landed in the batch after the last green run
while main refused at floor PREPARATION on an unrelated ArgvCommand seal break,
so its floor phase never executed and this row never surfaced on its own check.
That is the same masking that hid #9022's roster defect in the same batch -- two
independent defects merged behind one preparation refusal, which is the argument
for consuming every gating cause on your own run rather than only the rows you
meant to change.

WHAT MOVES: the file to dag/test/claim/long/ with its module renamed, one
WitnessExclusionRow carrying the measurement and the local recipe, and the
provider fixture's prose pointer updated so it does not name a module path that
no longer exists.

WHAT DOES NOT MOVE: both arms, their assertions and the whole authored rationale
are untouched. This is a home change and a declared rung drop, not a repair and
not a weakening of what the witness claims.
gunbai-bot Bot added a commit that referenced this pull request Aug 23, 2026
…ather than by widening the seal (#9031)

* Convert the one ArgvCommand site the seal missed, through a builder rather than by widening the seal

main does not compile. `extdeps.exec.command` `ArgvCommand` became `sole_constructor
{ program, arguments }` in #8919, and its carrier states the invariant: "Every
ArgvCommand construction site in the corpus was converted in the same change, because
a partial seal is a dual-authority interval rather than a weaker seal."

One site was not. `gunbc.runner_slot_provision` `observe_runner_slot_members_wet` still
built `ArgvCommand { argv: ["ls", "-1", actions_runner_base_dir] }`, producing four
distinct diagnostics and refusing the floor at strict-preparation — so no witness ran
and no `required-floor` counter line was emitted at all.

Not a missed conversion by that PR. #8992 ADDED this site after #8919 was authored and
before it merged; neither touched the other's lines, so git merged both without a
conflict and the corpus broke on semantics. A clean merge is not coherence.

WHAT WAS DELIBERATELY NOT DONE: `runner_slot_provision` is not added to `argv_command`'s
`admit_callers`. That list is forty named builders, each for ONE operation of ONE tool,
homed in that tool's own extdeps module; admitting a product module would defeat the
wall rather than satisfy it.

A MODELED ALTERNATIVE WAS LOOKED FOR FIRST and does not exist at this layer. `ls -1 |
parse` is a shell-ism, so the right first question was whether the substrate already
answers "what entries are under this path" without shelling out. `dag/extdeps/filesystem`
carries posix `EntryKind` but no listing operation, and the `list_dir` in
`src/v2/extdeps/file_system.dag` is the v2 realization surface, not reachable from a v1-era
`wet` observation running over `LocalExec`. So the builder is authored rather than the
listing being re-modeled, and the call site keeps enumerating BY DIRECTORY — which its own
carrier requires, since probing the desired names could only ever return a subset of what
was already intended and would make an out-of-band slot invisible.

`-1` is baked into the builder with its reason, following `rm_force_command`'s precedent:
ls(1) columnates to a terminal and emits one-per-line to a pipe, so a caller that does not
pass `-1` depends on where its output happens to go. It does not recurse and does not
include dotfiles; a caller needing either needs its own name, not a flags parameter.

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

* Enrolled-but-never-planned is not a parking spot: it refuses the whole floor

The ArgvCommand repair in this branch unmasked a second main-red defect that had
never been observed, because every run since it landed died at strict preparation
before the fold could reach it.

gunbc#9022 enrolled three compile_accepted_unevaluable_program_control identities
in the expected-red roster while their file declares ReadsLiveTree, so the
DeclinedLiveTree arm declines them (declined_live=899) and they are never planned.
The floor requires every ENROLLED identity to be observed among the EXECUTED
claims, so the run refuses with cause=ExpectedRedIdentityDidNotExecute count=3.

Chunk 21's own annotation states the opposite belief -- "until it lands they are
held here so that admission does not red main" -- and execution refutes it twice:
on #9022's own run 32644795043, which was merged red, and again on run 32649496046
here, where it was the sole floor refusal once the seal stopped masking it.

The precondition that enrolment was authored against has not landed: gunbc#8977 and
gunbc#8982, which delete the decline arm, are both still open.

The rows are correct and stay in chunk 21. Only their LIVENESS is wrong, so they
join the file's existing exclusion in floor_expected_red_is_live -- the mechanism
already used for the mock-totality family -- which keeps the rows for provenance
while removing them from the live roster.

Verified by execution, not by reading the predicate: each of the three returns
excluded (exit 1) through --claim-run, and an arbitrary unrelated identity returns
live (exit 0), so the predicate discriminates rather than excluding everything.

The exclusion is a coupling, not a note: whoever lands #8977 or #8982 must delete
these three exclusions in the same change, or the rows will execute while excluded,
count as ordinary failures, and red the build.

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

* The srv4 activation witness re-spelled two programs the seal then moved

Third blocker on main, and the first one visible only after the other two
cleared: with preparation passing and the roster refusal gone, the fold reaches
this row and it returns false.

  FAIL test.claim.runner_host_deploy.srv4_enables_its_declared_runner_instances

The cause is one conjunct, and it is not the width axis this row is otherwise
about:

  && string_contains(s: enable, pattern: "'sudo' 'systemctl' 'enable' '--now'")

That was correct against the hand-built argv the row was written for. #8919
routed runner_enable_command_argv through extdeps.systemd.systemctl
systemctl_enable_now_command, which mints through the sealed argv_command with
sudo_binary_path (/usr/bin/sudo) as the program and inserts sudo's
non-interactive flag -- so the rendered prefix is no longer the four words the
pattern names, and #8919 updated two lines of this file without reaching this
one.

WHAT I CHECKED BEFORE CHANGING ANYTHING, because a witness edited to match the
code it guards is worse than a red one. Quoting was the obvious suspect and it
is NOT the cause: shell_quote single-quotes unconditionally (emit_test's
shell_quote(arg: "plain") == "'plain'" pins it), so the unit-name conjuncts still
match. Admission was the other suspect and it is not the cause either: the
fixture receipts bind correctly on every arm admit_runner_activation checks --
instance host, managed_unit against intended_unit, all six verified flags, pool
host, and slice_unit against intended_compile_pool -- so the command is READY and
`enable` is a real render rather than the refusal arm's "".

THE REPAIR CITES RATHER THAN RE-SPELLS. The old pattern was a third authoring of
facts extdeps.sudo.elevation and extdeps.systemd.systemctl already own, which is
why it rotted without anyone touching it (DESIGN section 3). The conjunct is now
two, deriving the program spellings from those authorities and split because they
assert different things: that activation ELEVATES NON-INTERACTIVELY, and that it
reaches systemctl's ENABLE --NOW.

WHY THIS IS NOT measure() == measure(). `enable` and `--now` stay literal, and
they are the discriminating half: they are systemctl's own operands, spelled
inline by the builder rather than read from any row, so a command that carried
the units without the verb still fails here. What the derived halves buy is that
a future re-homing of the sudo or systemctl spelling moves the assertion with the
authority instead of leaving a fourth copy to rot.

Not verified locally: the same stale-binary limit recorded on the previous commit
applies. CI is the authority.

* join is not a std.types export: the two derived patterns build with concat

Preparation refused on the previous head with a single diagnostic --
  runner_host_deploy_witness_test.dag:4:33: name 'join' not found in module
  'std.types'
-- so the import I added to carry the derived invocation patterns named a symbol
that module does not export. The witness already builds every other composed
pattern with concat and needs no import for it; the two new ones now do the same.

This is my own defect from the previous commit, not new fallout: the repair it
carries is unchanged, only its spelling of string concatenation. The stale-binary
limit recorded there is exactly why it reached CI to be caught -- a local compile
under a binary that predates the seal cannot answer questions about this tree.

* Quarantine the qualified-spelling witness, and file the defect it uncovered

Operator ruling 2026-08-23: kill/quarantine it and find out why. Both halves are
here -- the move, and the measurement that makes this a filed defect rather than
a cost row.

#9031's run 32657761997 was otherwise clean (failed=0, stale_quarantine=0,
interrupted_before_verdict=0) with ONE non-pass:

  COMPLETED-OVER-COST-REQUIREMENT
  qualified_spelling_takes_the_shared_layer   wall_ms=57337 cpu_ms=57193
  [floor-claim-memory] grew rss by 1.22GB (to 11.70GB)

11x the 5000ms executor fail-stop. The fail-stop is doing its job: at 11.70GB
this claim genuinely threatens the process it runs in.

THE SPLIT THAT WAS SUPPOSED TO FIX THIS DID NOTHING, and that is the finding.
#8984's own note records the conjoined two-arm claim at 1.22GB RSS growth
against 0.12GB for the whole rest of the roster, and splits the arms into
separate test fns on the reasoning that two live compile_dag_rust_emit_check
calls sat inside one claim. Measured after the split, the QUALIFIED ARM ALONE
grows 1.22GB -- the identical figure -- and the bare arm appears in no over-cost
or memory line at all. So the entire cost was always the qualified arm. It is
not two compiles; it is the qualified-name resolution path specifically, which
is the path #8984 exists to repair.

WHY QUARANTINE AND NOT A COST ENVELOPE. An envelope large enough to admit 57s
and 1.22GB would admit the defect rather than measure it. The dissolution
condition on this row is therefore the REPAIR, explicitly not an envelope, so
nobody closes it by raising the ceiling.

WHY IT REACHED MAIN UNSEEN. #8984 landed in the batch after the last green run
while main refused at floor PREPARATION on an unrelated ArgvCommand seal break,
so its floor phase never executed and this row never surfaced on its own check.
That is the same masking that hid #9022's roster defect in the same batch -- two
independent defects merged behind one preparation refusal, which is the argument
for consuming every gating cause on your own run rather than only the rows you
meant to change.

WHAT MOVES: the file to dag/test/claim/long/ with its module renamed, one
WitnessExclusionRow carrying the measurement and the local recipe, and the
provider fixture's prose pointer updated so it does not name a module path that
no longer exists.

WHAT DOES NOT MOVE: both arms, their assertions and the whole authored rationale
are untouched. This is a home change and a declared rung drop, not a repair and
not a weakening of what the witness claims.

* Two zero-byte shell artifacts were committed at the repo root

`exit_code` and `{` are both empty files introduced by 96da63f. They are
redirect and brace-expansion debris from an interactive shell, not source, and
nothing references either as a path: the `exit_code` hits in the corpus are
shell-transport OUTPUT FIELD decodings (`exit_code: Int from "exit_code"` in
extdeps.shell.exec and friends), which name a captured field, not a file.

Removed rather than ignored. A .gitignore rule does not untrack a path already
in the branch, and leaving them would land two junk files in the repo root on
merge.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brian Searls <briansearls1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant