Skip to content

The compiler admits DUPLICATE FUNCTION DEFINITIONS in one module and silently resolves the call to the last one: a below-floor name-resolution defect with LIVE exposure, verified by execution, needing a fixture-boundary RED and a 4b row - #9481

Closed
briansrls wants to merge 2 commits into
mainfrom
session/sleek-gull-113

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session sleek-gull-113.
Pushing to session/sleek-gull-113 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

Brian Searls and others added 2 commits August 27, 2026 13:33
…use them, with a fixture-boundary RED and two live specimens repaired

A module declaring one name twice was ACCEPTED with zero diagnostics, and every
reference bound whichever declaration was written last. `v1.compiler.env`
`symbol_index_insert` is a `map_insert` keyed on the qualified name, so the second
declaration REPLACED the first in the symbol index -- two authorities for one fact
(DESIGN section 3) with no refusal anywhere. Names resolving is the ordinary compiler
floor, so this sat BELOW it (DESIGN section 4b): silent wrongness, outside the ladder
rather than a weak rung on it.

MEASURED BY EXECUTION, instrument named. Binary built from source at ddb7f53
(main tip), sha256 1b2eb218e6725f76..., mtime 2026-08-27T13:31:07Z -- not the
installed /usr/local/bin/gunbc, which is two days stale and proves nothing about
current main:

  fn probe_dup() -> Int { 1 }        fn probe_dup() -> Int { 1 }
  fn probe_dup() -> Int { 2 }        fn probe_dup_caller() -> Int { probe_dup() }
  fn probe_dup_caller() ...
  => 2  (the LAST definition)        => 1  (control)

The collision is NOT fn/fn only, which the original finding did not cover: a `data`
and a `fn` of one name share the same key, and a bare reference to the data evaluated
to `<fn thing>`.

LIVE EXPOSURE, denominator stated: 4163 tracked .dag files scanned, 2 modules carried
it, both repaired here.
  * dag/gunbc/systemctl_list_units.dag -- `data systemctl_list_units_all_states: String`
    shadowed by `fn systemctl_list_units_all_states(..)`, so the ListUnits `state:`
    argument bound the FUNCTION rather than "active,inactive,failed". A production
    module with two importers. The data row is renamed
    `systemctl_all_unit_states_selector`; the function keeps its name, which is what
    both importers call.
  * dag/test/claim/spark_managed_grant_reconcile_witness_test.dag -- one `data` row
    declared twice with an identical value; the second is deleted.
The deletion IS the census: with the dotted-name form of the scan (`service shell.Find`
is one name, not `shell` repeated) zero duplicates remain.

THE WALL. `v1.compiler.resolve` `check_duplicate_declarations` folds each module's OWN
item list and emits a blocking `DuplicateDeclaration` on the second declaration of one
name. It reads nothing outside the module it judges -- no corpus walk, no import
resolution, no type information -- so the class is decidable from one module's source,
which is what makes it a wall rather than a ratchet (DESIGN section 5). Rung: R2,
mechanically preventable. The R4 climb is a keyed declaration roster at ingestion that
cannot HOLD two entries for one name, and that is the row's next-rung trigger.

THE EVIDENCE, and where it does NOT live. The first cut of this change put the RED in
`v1.compiler.compiler_tests_rust`. That carrier is DEAD: its output is
`#[cfg(test)] mod compiler_tests;` inside the v1-compiler crate, while the only test
package that runs is the separate v1-compiler-tests, so cfg(test) is never set for it
and the block is not compiled -- a mechanism `src/v1/05_emit_rust.dag`
`checkpoint_table_bypasses_identity_note` already records a prior lane discovering the
same way. It was reverted, and the evidence is authored where an equivalent witness
demonstrably executes: dag/test/claim/duplicate_declaration_wall_witness_test.dag,
`SubstrateInputsOnly`, driving the real v1 compile over inline fixtures through
`compile_dag_rust_emit_check`, following `checkpoint_identity_keying_witness_test.dag`.
Two REDs (fn/fn and data/fn) each paired with a control that differs from it by exactly
the duplicated declaration and nothing else -- `compile_dag_rust_emit_check` answers
false for ANY hard diagnostic, so the discrimination is carried by the PAIR, not by
either row alone. All four measured `true` on the post-wall binary
(sha256 1659e35605...), and per DESIGN 4b(4) the controls stay enrolled: they are the
executing evidence that the rung is real.

THE BOUND THAT MATTERS MOST IS NOT THE COMPILER'S, and it is written into both the
witness and the gap-analysis row: where two definitions of one name are semantically
EQUIVALENT, no behavioural witness can discriminate them whichever wins.
Green-by-execution proves BEHAVIOUR and is structurally blind to duplicate AUTHORSHIP
-- which this repository leans on everywhere, and which is why the live specimens above
passed every witness over them.

Also here: the DESIGN 4b row in
docs/plans/compiler-guarantee-recovery-gap-analysis.md (invalid state, harm,
distinguishing facts vs the cross-module last-import-wins row, rung, ceiling with
reason, next trigger); a prose correction on
dag/test/claim/duplicate_definition_binding_probe.dag, which asserted this refusal
before it existed and is still `DeclinedLiveTree`, so what flipped is its SUBJECT and
not its reach; and the fourth-lane receipt in `v1.std.core`
`compiler_diagnostic_seed_projection_note`, which its own text obliges whenever the
diagnostic coproduct grows -- cli_run.rs fn census FLAT at 895 on both sides,
2 added / 0 removed, 0 added lines declaring a fn.

v1 SEED PURPOSE, since the change touches the seed: the wall is a floor repair on the
name-resolution path the v2 self-host program compiles through, and the two repaired
specimens are corpus modules that self-host must emit correctly. Nothing here grows the
seed's capability -- the two cli_run.rs lines are the mechanical consequence of the
coproduct gaining a member.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n/main moves under other merges, so the named command reported a diff nobody authored (review 56644)

The receipt in `v1.std.core` `compiler_diagnostic_seed_projection_note` names three
commands and requires that each figure be re-derivable from them. All three were keyed
to `origin/main`. THAT REF MOVES. Measured on this branch, hours apart, with no edit to
the change being certified:

  git diff --numstat origin/main -- src/v1/stage0/src/cli_run.rs
    when authored: 2 added / 0 removed
    at review:     6 added / 64 removed

The 64 removals are MAIN's work, not this branch's. A diff has two sides and only one of
them is this branch, so a receipt keyed to the moving side reports a figure that changes
without anyone touching the diff it describes -- which is exactly the rot this row exists
to make visible, arriving through the REF rather than through the NUMBER. That is why it
survived three prior lanes: each author re-measured honestly and the command was already
wrong.

REPAIR: every figure is now measured against `$(git merge-base origin/main HEAD)`, this
branch's own fork point, which does not move; and the base commit is NAMED (ddb7f53)
so the reading stays reproducible after a later merge. Re-measured against it -- fn
census FLAT at 895 on both sides, 2 added / 0 removed, 0 added lines declaring a fn.

THE EARLIER LANES' FIGURES ARE NOT RE-BASED OR AMENDED. They were correct against the
main they named at the time, and rewriting them would destroy the evidence that a receipt
keyed to a moving ref decays -- which is the finding. What changes is the command the
NEXT lane runs.

v1_std_core.rs is the regenerated mirror of the edited note; required-regen reported it
as the only drifting surface and it is installed from the candidate tree.

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

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Closing as contained by #9452, which merged at 12:56:46 as 26eee4b8e7. Not withdrawn as wrong — every line of it is already on main.

Verified per-file against origin/main (head 3bbd53c05a5) rather than by diffing the branch, because the branch forked before #9452 landed and both of the usual instruments mislead here:

  • git diff origin/main...HEAD (three-dot) diffs against the merge base, so it shows this PR adding the wall.
  • git diff origin/main HEAD (two-dot) is dominated by 628 commits of branch staleness — 168 files, ~26.7k deletions — which is the branch reverting main's newer work, not this PR's content.
  • GitHub's baseRefOid reports the current main tip, which is not the fork point either. git merge-base --is-ancestor 26eee4b8e7 <branch> is false.

The containment test that actually answers it, per file:

file vs main
dag/gunbc/systemctl_list_units.dag identical
dag/test/claim/duplicate_declaration_wall_witness_test.dag identical (present on main)
dag/test/claim/duplicate_definition_binding_probe.dag identical (present on main)
dag/test/claim/spark_managed_grant_reconcile_witness_test.dag identical
src/v1/03_resolve.dag identical — check_duplicate_declarations present
src/v1/stage0/src/v1_compiler_resolve.rs identical
src/v1/00_core.dag differs (stale branch) — all added lines present on main
src/v1/stage0/src/cli_run.rs differs (stale branch) — all added lines present on main
src/v1/stage0/src/v1_std_core.rs differs (stale branch) — all added lines present on main

Every line this PR adds — the DuplicateDeclaration variant, its span and message arms, the cli_run.rs projection rows, the 00_core.dag coproduct arm, and the seed-projection receipt — resolves as already on main. Nothing here is absent from the tree.

No v1-seed admission argument is owed. The seed change already merged with whatever argument #9452 carried; writing one now would be a second admission for one landed fact.

The wall is doing its job right now. check_duplicate_declarations is what refuses gunbc.fleet_fan_wiring_witness on current main — a duplicate that has sat latent since 9f08b05c56 and only became blocking when this wall could see it. Repaired separately in #9498. Worth recording that a local entry-grain compile of that file returns identical output with the duplicate present and cut (0 blocking, 272 advisory both arms): this class is visible only in the floor's strict preparation, which is how it reached main in the first place.

@gunbai-bot gunbai-bot Bot closed this Aug 27, 2026
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