Skip to content

main does not resolve: #10236 re-declared two failure-mode rows, so the module has 84 declarations under 82 names - #10279

Closed
gunbai-bot[bot] wants to merge 1 commit into
mainfrom
session/neat-swift-219-dupfix
Closed

gunbai-bot[bot] wants to merge 1 commit into
mainfrom
session/neat-swift-219-dupfix

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

What is broken

origin/main's dag/gunbc/recurring_failure_mode.dag carries 84 declaration lines under 82 distinct names. absence_classifier_default_bucket and green_reported_over_a_population_the_instrument_does_not_own are each declared twice, byte-identical, and the compiler refuses the module:

duplicate declaration '<name>' in module 'gunbc.recurring_failure_mode'
-- a second declaration of one name silently replaced the first

Reproduce on any tree:

git show origin/main:dag/gunbc/recurring_failure_mode.dag | grep '^data ' \
  | sed 's/^data \([a-z_0-9]*\).*/\1/' | sort | uniq -d

This blocks every lane that merges main and regenerates. Two lanes hit it independently by execution — a merge of main that could not regenerate, and a second lane's regen. Any lane whose regen is green right now is green because it has not merged main since cfe19ea7f48.

The fix

#10236 (cfe19ea7f48) appended the pair immediately above the roster; the originals at their authored positions are untouched. This PR deletes the appended copies.

Why it matters beyond the outage (DESIGN §3)

Two declarations of one identity are two authorities for one fact. They agree today, so no experiment can discriminate them and nothing in the projection fails — which is exactly why the state survives. It arms on the first divergent edit to either copy, after which whichever declaration the resolver binds becomes the answer with no diagnostic.

Why no instrument caught it

Every instrument in play was a set check. A declaration symmetric-difference against the roster returns empty for a duplicate, because a set discards multiplicity, and it was reported clean twice on that basis. A row generator keyed by identity deduplicated the pair with no signal — correct output by luck, because the bodies happen to match.

The sound test is the multiset: the gap between declaration lines and distinct names is the duplicate count.

This is an instance of green_reported_over_a_population_the_instrument_does_not_own — one of the two rows that got duplicated.

Correction to an earlier figure in this PR

An earlier revision of this description said 85 declarations under 83 names. That over-counted by one on both sides: the pattern admitted data recurring_failure_mode_roster: List<RecurringFailureMode> = [ — the container counted as one of its own members, because the List declaration contains the type name. The true figures are 84 and 82. The difference of 2, which carries the finding, is invariant under either pattern.

Correction to the "compare the multiset" lesson

One instrument reported here as set-shaped was not. A grep -oP '^data \K\w+' | sort diff — plain sort, no -u — is already a multiset diff, and it did fire, printing both identities where sort -u prints nothing. So the multiset comparison was not blind here; the reading was.

A multiset difference emits a bare name list, and "X is in child and not in parent" is ambiguous between two different facts: a new name (0 → 1) and an extra copy (1 → 2). Both render as the identical line.

The upgrade is therefore a different one line: report a count per name — parent_n -> child_n per identity — so 0 -> 1 reads as a new class and 1 -> 2 reads as a duplicate and cannot be mistaken for one. The general rule: a diff whose output shape is a set of names cannot express a defect class that lives in counts, no matter which comparison produced it.

The genuinely set-shaped blind instruments were the declaration-versus-roster symmetric difference and the identity-keyed generator that deduplicated the pair with no signal. Only the compiler refused.

Do not verify this PR by byte-identity — verify the merge ref

An earlier line here said the file is byte-identical to cfe19ea7f48^. That was true when written and is no longer a usable check: main has since taken more rows (#9981 among them), so byte-identity against that historical blob now FAILS, and its failure reads as "this fix reverts someone's row." It does not — the baseline moved.

The property that survives a moving main is dup=0 on the merge ref:

git fetch -q origin pull/10279/merge
F=dag/gunbc/recurring_failure_mode.dag; M=$(git rev-parse FETCH_HEAD)
L=$(git show $M:$F | grep -c '^data .*: RecurringFailureMode')
N=$(git show $M:$F | grep -oP '^data \K\w+(?=: RecurringFailureMode)' | sort -u | wc -l)
echo "$L lines / $N names / dup=$((L-N))"

Measured after main moved to 8b2323f4145: 82 / 82 / dup=0.

Three PRs independently fix this (#10277, #10278, #10279); all three merge refs read dup=0, so any one suffices and double-landing is safe.

Structural follow-up (not this PR)

The one-row-per-file split under way in #10206 makes this state unwritable: two files cannot share a name. That is a climb to structural impossibility for this class, and it removes the possibility rather than this instance.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uy2Ug5E9eDu3nkWxd5Si5v

…a duplicate declaration silently replaces the first

origin/main's dag/gunbc/recurring_failure_mode.dag carries 85 declaration lines
under 83 distinct names. absence_classifier_default_bucket and
green_reported_over_a_population_the_instrument_does_not_own are each declared
TWICE, byte-identical, and the compiler refuses the module:

  duplicate declaration '<name>' in module 'gunbc.recurring_failure_mode'
  -- a second declaration of one name silently replaced the first

Two lanes hit this independently by execution: a merge of main that could not
regenerate, and a second lane's regen. Any lane whose regen is green right now
is green because it has not merged main since cfe19ea.

The pair was appended by #10236 (cfe19ea) immediately above the roster; the
originals at their authored positions are untouched. Both copies are
byte-identical, so no authored content is lost and the projection is unaffected
-- the roster names each identity once. This file is now byte-identical to
cfe19ea^, so nothing from #10269's amendment or any earlier row is reverted.

Two declarations of one identity are two authorities for one fact (DESIGN 3).
They agree today, so no experiment can discriminate them and nothing in the
projection fails -- which is exactly why the state survives. It arms on the
first divergent edit to either copy, after which whichever declaration the
resolver binds becomes the answer with no diagnostic.

Why it was not caught: every instrument in play was a SET check. A declaration
symmetric-difference against the roster returns EMPTY for a duplicate, because
a set discards multiplicity, and it was reported clean twice on that basis.
The sound test is the MULTISET: the gap between declaration lines and distinct
names IS the duplicate count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy2Ug5E9eDu3nkWxd5Si5v
@gunbai-bot gunbai-bot Bot changed the title main does not resolve: #10236 re-declared two failure-mode rows, so the module has 85 declarations under 83 names main does not resolve: #10236 re-declared two failure-mode rows, so the module has 84 declarations under 82 names Sep 3, 2026
@gunbai-bot

gunbai-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #10278, which landed the identical +0/-4 delete at 21:01:16Z. Main is repaired: 1af8892e143 measures 83 declaration lines / 83 distinct names / dup=0, and that count includes #9981's row, so nothing was reverted to get there.

Three lanes independently built this same fix (#10277, #10278, #10279). That is a coordination cost, not a risk — all three merge refs read dup=0, so any one sufficed. Closing mine rather than landing a second no-op.

Verification note for anyone reading this later: do not check a fix like this by byte-identity against a historical blob. Main moved twice while these were open, so byte-identity now fails and its failure reads as "this reverts someone's row." The property that survives a moving main is dup=0 on the merged tree.

— sent from neat-swift-219

@gunbai-bot gunbai-bot Bot closed this Sep 3, 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.

0 participants