Skip to content

Design a required phase that compiles one v2 entry: today an emission break reached main and no gate could see it - #9035

Merged
briansrls merged 16 commits into
mainfrom
session/deep-otter-200
Aug 24, 2026
Merged

briansrls merged 16 commits into
mainfrom
session/deep-otter-200

Conversation

@briansrls

@briansrls briansrls commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

What escaped

On 2026-08-23 gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/03_ingest.dag refused outright on main — no emitted tree, no cargo log — while every required phase stayed green for hours. It was found by a hand-run probe, not by a gate.

The required run has three phases: the src/v1 .dag parse sweep, --required-regen, and the witness floor. None of them compiles a v2 entry. The specimen was a trailing // annotation block with no declaration after it, authored under dag/test/manual/ — which no required phase parses either. Two independent misses, one artifact.

The measurement that decides the design

An --entry compile is scoped in what it EMITS (the reference-derived closure) and whole-tree in what it PARSES: every indexed module outside the closure enters the name census, so the census parse reaches all of dag + src/v2 whichever entry is named. Measured locally on this branch (both of the day's fixes resolved in — see Baseline below):

entry closure census emitted wall
dag/std/abi.dag 4 3,847 9 135s
src/v2/compiler/03_ingest.dag ~180 ~3,670 176 349s

And with the real specimen planted back into dag/test/manual/, the small entry refuses, naming the file and byte range:

required-v2-emission: entry=dag/std/abi.dag closure=4 census=3847 emitted=0 blocking=1 advisory=0 wall_ms=114902
required-v2-emission: REFUSED v2-emission compile of dag/std/abi.dag produced 1 hard diagnostic(s):
  source annotation names no subject: no module item follows it. Move it above the declaration
  it describes. (dag/test/manual/process_argv_expansion_receipt_test.dag:4217-4274)

So 03_ingest's extra 214s buys emission coverage of the compiler's own closure — not coverage of the class that actually escaped. The entry is dag/std/abi.dag, and it is a .dag row (gunbc.ci_layer_roots required_v2_emission_entries, a List<String>) so re-deciding that trade is an authoring change, never a Rust edit.

The line, and what it does not catch

The refusal predicate is not new: it is v1_compiler_compile stage0_self_compile_refusal_message, the same authority gunbc compile already stops on — a blocking diagnostic, or an empty emitted file set. Restating "blocking, or zero files" here would be a second representation of one rule.

Advisory diagnostics are counted and reported, never refused on. 03_ingest carries 503 advisory and 0 blocking today; a gate on any diagnostic is permanently red and useless.

Named rather than left to be inferred, this phase does not catch:

  • a rustc error in the emitted tree (nothing here compiles the emission);
  • a semantic regression that still emits;
  • an emission break confined to a closure the configured entry does not reach.

A fourth, which arrived as supporting evidence from a peer, failed on execution, and then turned into something sharper than either account:

  • a name whose resolution depends on which entry you compiled. gunbc.command_runner calls stat_owner_user_name_command with no import of extdeps.tools.stat (repaired in command_runner: the import #8919's builder call needs, and the census that closes the class at two #9045). Measured on one tree with one binary:

    entry closure verdict on the same unchanged file
    dag/gunbc/fleet_converge_cli.dag 665 sources no diagnostic
    a probe importing gunbc.command_runner 96 sources function 'stat_owner_user_name_command' not found in scope

    A peer isolated the variable: adding one import of gunbc.host_effect_realize (which does import extdeps.tools.stat) to a firing 397-source entry takes it to 654 sources and the diagnostic disappears, nothing else changed. I reproduced the firing direction independently at 96 sources rather than taking it on report.

    A bare name resolves against the flat namespace of whatever modules happen to be in the closure. So compile's verdict on a module is not a property of that module — it is a property of the entry someone chose. The interpreter resolves through each module's own imports and rejects the call either way, so the two paths disagree and the eval path is the stricter one: compile admits what eval refuses.

What that means for this phase, answered here because a reviewer will ask. It does not claim to answer "is the corpus name-clean" — on this compiler that question has no single answer to gate on, only an answer per entry, and a green over one entry could in principle be produced by an unrelated import entering that entry's closure. So:

  • a green here licenses exactly one statement — the configured entry emitted — and must not be cited as name-resolution coverage;
  • this is a property of the compiler's resolution, not of the gate, and widening the entry roster does not fix it (a bigger closure resolves more bare names, not fewer — the 665-source entry is the silent one);
  • the entry-independent check that would close it is a different mechanism, not a widening of this one: resolve each module against its own imports, which is what the eval path already does. That is this class's next-rung trigger, and it is not this PR.

Named at this length because the failure mode is inviting: an emission gate looks like it covers resolution, and on this compiler it cannot.

For scale on that class rather than on this gate: main's own green run 32664434197 carries 59 distinct unresolved symbols across 137 no such function occurrences (known_red_runtime_errored=143), measured by a peer — of which at least 8 are deliberate red fixtures by name, and the triage is not done. Those rows are enrolled as expected reds and the floor does not gate on that counter, so they are neither passing nor failing: absent while being counted as a roster. This phase does not touch them.

Say the closure/census one out loudSay the closure/census one out loud, because the PR's own title invites the wrong reading.** "Compiles one v2 entry" sounds like emission coverage; the gate's reach is the census (whole-tree parse of dag + src/v2), not the closure (4 modules under the enrolled entry). A break confined to a closure no configured entry touches stays invisible to it. Widening reach is a row on required_v2_emission_entries and a cost decision — 03_ingest's closure costs +214s.

It does subsume a whole-tree .dag parse sweep of dag + src/v2, because the census parses all of it.

That retires the "do not add a second parse sweep beside the existing one" objection rather than working around it: the choice does not exist. One phase, not a phase plus a sweep. And it sharpens the diagnosis of what was missing — the gap was never about which directory a sweep walks; it was that no required phase ran the compiler over the v2 source roots at all, so dag/test/manual/ was unreached for the same reason every other out-of-closure module was.

The gate refuses on main, right now, on the real break

Not a planted fixture and not a hypothetical. dag/test/manual/command_runner_local_argv_receipt_test.dag on current origin/main still ends with a standalone // block and no declaration after it — the original 2026-08-23 specimen. #9027 moves the test fn back under that block and is still open.

Substituting only that file's main content into an otherwise-passing tree and running the phase:

required-v2-emission: EmissionRefused entry=dag/std/abi.dag closure=4 census=3849 emitted=0 blocking=52 advisory=0 wall_ms=128832
required-v2-emission: EmissionRefused entry=dag/std/abi.dag phase=emit cause=… produced 52 hard diagnostic(s)
required-v2-emission: entries=1 not_completed=1

Restore the file and the same command reports EmissionCompleted … emitted=9 blocking=0. So the phase's red and green are both produced by real main content, with one file as the only variable.

And main's own required run is green while carrying it. That is the whole argument for this PR in one pair of facts: the break is on main, the required run cannot see it, and this phase sees it in 125s.

(The specimen is a parse break, which is the class the census actually covers. A resolution defect such as #9045's stranded import is not this class and is not caught — see the fourth limitation above, where that distinction is worked out at length.)

Evidence, executing

claim_executor --required-v2-emission-selftest — two controlled fixture roots under fixtures/v2_emission_gate/, differing in one thing. red/subject.dag carries the real specimen (trailing block, no following declaration); green/subject.dag has the same block moved above the declaration it describes, the placement DESIGN §4c admits. Red must refuse on the annotation cause (a refusal for any other reason is reported as a failure); green must emit. Runs in milliseconds — its oracle is authored independently of the corpus it will judge.

Mutation-confirmed, not just observed green:

# red fixture mutated so the block leads a declaration
required-v2-emission-selftest: FAIL selftest RED did not refuse: 6 file(s) emitted from the trailing-annotation fixture
# restored
required-v2-emission-selftest: OK red fixture refused on the annotation cause, green fixture emitted

One producer, not two callers

The gate consumes the same emission transaction as the cargo board. It did not when first pushed, and this is worth recording as an instance rather than a fixed bug:

The gate, as reviewed and approved, was resolving a different module set than the board. It ran under the strict pool index; the board runs --dependency-pool-index primary-precedence. So the gate could have gone green on a tree the board refuses — which is the exact hole the gate exists to close, present in the gate itself. It was found by asking one question about provenance ("are those the same producer, or two callers of one?"), not by any check, and no approval would have caught it: both halves were correct in isolation and only the arrow between them was missing.

The gap in detail: the phase was a second caller reproducing the closure load, the census assembly, the refusal check and the silent-pick gate beside gunbc compile's — and it had already drifted on one of the three parameters, resolving under the strict pool index where the board runs --dependency-pool-index primary-precedence. A gate that resolves different modules from the board can green while the board refuses.

The transaction now lives in cli_run compile_entry_emission, and both consumers call it:

  • gunbc compile --entry <M> --target rust — the board's producer, invoked by docs/probes/curated_cargo_probe_one.sh, whose EMIT_REFUSE verdict is that command exiting nonzero;
  • claim_executor --required-v2-emission — the gate, which drops the tree and reads only the disposition.

The refusal predicate inside it is v1_compiler_compile stage0_self_compile_refusal_message (a blocking diagnostic, or an empty emitted file set) plus the CLI's silent-pick gate, because that gate is also part of the board's nonzero exit — a phase that skipped it would green on a tree gunbc compile refuses.

Verified by execution that the CLI path is unchanged by the extraction: dag/std/abi.dag → 4 closure / 3,847 census / 9 emitted; the board's exact invocation on 03_ingest → 177 emitted by the compiled: line, 0 blocking, 503 advisory, exit 0 (176 by find -name '*.rs' — the known two-instrument split).

Three states, not two

Option<refusal> beside a counts line was a two-state carrier asked to express three, and the third is the dangerous one: a run that never reached the compiler rendered as emitted=0 blocking=0, byte-identical to a clean run of an empty subject, told apart only by whether an adjacent line existed. A parser reading one line at a time cannot make that distinction.

EntryEmissionDisposition is now Completed { emitted_count } / Refused { phase, cause } / NotExecuted { earlier_phase, cause }. The tag is on the counts line, and the count fields read n/a — deliberately not 0 — on any path that never ran. All three arms confirmed by execution:

required-v2-emission: EmissionCompleted entry=dag/std/abi.dag closure=4 census=3847 emitted=9 blocking=0 advisory=0 wall_ms=120784
required-v2-emission: EmissionRefused   entry=dag/std/abi.dag closure=4 census=3847 emitted=0 blocking=1 advisory=0 wall_ms=125344
gunbc compile: entry-read: entry file does not exist: dag/std/does_not_exist.dag

The invariant is that emission COMPLETED — never a file count. A legitimate compiler change may alter a closure's size, so emitted == 177 is a change detector wearing an invariant's clothes.

Enrolment, and what it is not credited for

Enrolled as --required-ci phase 3, ordered ahead of the floor, on the relayed operator ruling. Measured cost +135s against the floor's ~30–40 minutes.

The ordering is report order only: the phases are independent and every one runs after an earlier failure, so this does not stop the floor starting on an unemittable tree. Making it an early prerequisite (an exit before the floor) would change this mode's stopped-line audit design and is a scheduling decision left to the operator with the number attached. No second binary-provenance path: it runs in the same claim_executor process from the same built artifact as the other phases.

This gets admission-integrity credit and zero rustc-convergence credit. It makes "the canonical v2 board subject emits" an admission invariant; it does not move the board.

Dissolution trigger — carried on gunbc.ci_layer_roots required_v2_emission_dissolution, not just stated here: when the authentic self-host product receipt is itself required on every admitted candidate and carries this exact emission boundary (same producer, stopping before cargo), the standalone gate dissolves into it and the row, its host reader and the phase delete together. Without that trigger, emission preflight / self-host probe / product receipt / cargo census become four permanent implementations of one transaction.

CI history on this branch, corrected

An earlier revision of this section said this PR's CI would stay red until #9036 landed. That was wrong, and it is rewritten rather than annotated. #9036 closed unmerged at 17:44Z because #9031 absorbed its content, not because the fix was abandoned — this branch was carrying a pre-17:00 merge of #9031 that had its argv half and not its expected-red half.

What the two runs measured:

  • 32657155521 (head 6570ec5) — phases_run=4 failed=1, the single failure being floor refused: ExpectedRedIdentityDidNotExecute count=3 on the three compile_accepted_unevaluable_program_control identities. Parse OK (51 files), regen first_generation_equal=true, zero argv errors. The v2-emission phase completed on that run (see above) — the red was the stale merge, not this change.
  • Current head merges origin/fix/argv-command-ls-seal@a47aded, whose floor_expected_red_is_live now excludes those three identities.

This branch therefore carries #9027 and #9031 to get a measurable baseline. Every number above was taken on a tree carrying both and stays valid — only the vehicle needs cleaning. This PR is not to be merged while its diff carries them: landing three repairs under one title makes each harder to revert independently. Once they are on main, main is merged in here and the diff becomes this change alone; the measurement is not re-taken, because it was taken on exactly that content.

gunbc-ci-auto-heal and others added 5 commits August 23, 2026 15:11
`gunbc compile --entry src/v2/compiler/03_ingest.dag` REFUSES on current main
(faf6583) with `EMIT_REFUSE` and no cargo log at all. Measured this morning
at 907f19c the same entry emitted 177 files and 316 coded errors, so the
board's subject stopped being producible at some point in today's merges.

The cause is one file. §4c admits an annotation only as a standalone leading
`//` block attached to a module-scope declaration; #8919 left a 62-line block at
end-of-file with no item following it, and the compiler correctly refuses every
span of it -- "source annotation names no subject: no module item follows it".
A corpus-wide scan finds exactly one such file, so the population is closed.

The repair moves the block above the file's final declaration. Every annotation
line is preserved verbatim (diff of sorted `//` lines is one added `//`
separator); no prose is rewritten, dropped, or summarised, because a block
deleted for one reason takes everything in it unless its contents are enumerated
first.

WHAT THIS SAYS ABOUT THE GATE, which matters more than the fix. This is the
SECOND instance of this class today: #8976 landed an indented `//` inside a
match arm this morning and broke the floor corpus-wide for 74 minutes. That one
was caught because the file was floor-enrolled. THIS one sits in
dag/test/manual/, which no required phase parses -- the required run's three
phases are the src/v1 `.dag` parse sweep, required-regen, and the witness floor,
and none of them compiles a v2 entry. So an emission-breaking change landed on
main and every gate stayed green.

RUNG, honestly: this change is a repair of one instance and NOT a climb. The
class remains writable and undetected. Its next-rung trigger is a required phase
that compiles at least one v2 entry -- the emission path has no gate at all
today, which is why the only instrument that found this was a hand-run probe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ather 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>
…e 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>
…efusing only where no tree comes out

The 2026-08-23 break: `gunbc compile --source-root dag --source-root src/v2 --entry
src/v2/compiler/03_ingest.dag` refused outright on main for hours while every required
phase stayed green. The required run parses src/v1 .dag, compares the regen mirrors and
folds the witness floor; none of the three compiles a v2 entry, so the emission path had
no observer. The specimen was a trailing `//` annotation block with no declaration after
it, authored under dag/test/manual/, which no required phase reads either.

`claim_executor --required-v2-emission` compiles the entries named by
`gunbc.ci_layer_roots` `required_v2_emission_entries` and stops the line on the compiler's
own refusal authority (`v1_compiler_compile` `stage0_self_compile_refusal_message`) — a
blocking diagnostic, or an empty emitted file set. Advisory diagnostics are counted and
never refused on: 03_ingest carries 503 of them today, so a gate on any diagnostic would
be permanently red.

`--required-v2-emission-selftest` is the phase's executing evidence: two controlled
fixture roots differing in one thing, the red one carrying the real specimen and the green
one the same block moved above its declaration. Red must refuse on the annotation cause;
green must emit. Mutation-confirmed — moving the red block above a declaration makes the
selftest FAIL.

NOT enrolled in `--required-ci` or witnesses.yml: changing what every PR must pass is an
operator decision, and the enrolment question goes up with its measured cost attached.
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 23, 2026 17:17
Brian Searls added 4 commits August 23, 2026 17:48
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.
…sition, and enrolment as a required phase

Finding 1 (the gate must consume the same producer as the board): it did not. The phase
was a second caller reproducing the closure load, the census assembly, the refusal check
and the silent-pick gate beside `gunbc compile`'s — and had already drifted on one of the
three parameters, resolving under the strict pool index where the board runs
primary-precedence. The transaction now lives in `cli_run` `compile_entry_emission`, and
BOTH the CLI's `--entry` single-target arm and the required phase call it. Verified by
execution that the CLI path is unchanged: abi 4/3847/9, and the board's own invocation on
03_ingest emits 177 by the `compiled:` line with 0 blocking / 503 advisory, exit 0.

Finding 2 (a preparation refusal must not render as emitted=0 blocking=0): `Option<refusal>`
was a two-state carrier expressing three. `EntryEmissionDisposition` is now
Completed / Refused { phase, cause } / NotExecuted { earlier_phase, cause }, the tag is ON
the counts line, and the count fields read `n/a` — deliberately not 0 — on any path that
never ran. All three arms confirmed by execution.

The invariant is emission COMPLETED, never a file count: a legitimate compiler change may
alter closure size, so `emitted == 177` would be a change detector.

ENROLLED as required-ci phase 3, ordered ahead of the floor, on the relayed operator
ruling. The phases stay independent — every one runs after an earlier failure — so this is
report order, not an early prerequisite; making it one is a scheduling decision left to the
operator with the number attached. Dissolution trigger carried on
`gunbc.ci_layer_roots` `required_v2_emission_dissolution`.
…ED, and name the enrolment authority beside the dissolution row

The comment beside --required-v2-emission-selftest was written before the enrolment and
survived the revision that added phase 3, so the diff carried prose asserting the phase is
opt-in beside code making it required. Rewritten, not annotated: two accounts of one fact
is what produced the contradiction. The ci_layer_roots row now states the enrolment
authority separately from the dissolution condition — how the gate ends is not the
admission that created it.
@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Both halves of review 55117 verified against the branch. First is fixed; second I am declining, with reasons.

The contradiction was real and is fixed (pushed). The comment beside --required-v2-emission-selftest was written in the revision before the enrolment and survived the one that added phase 3, so the diff carried prose asserting the phase is opt-in beside code making it required. Rewritten, not annotated — two accounts of one fact is what produced the contradiction in the first place. It now states the enrolment plainly, cites the relayed operator ruling and the +135s, explains what the two standalone flags are actually for (running the emission alone, or only its evidence, are real local actions — the same reason the src/v1 .dag parse sweep keeps its own bin beside its required phase), and records why the sentence was wrong so the next reader does not re-derive it. Good catch: exactly the premise-contamination class, in a comment a grep would land on.

On unbound_dissolution — declining, because it is answering a question the row does not ask. The scaffold-admission doctrine governs whether debt may be created, and that approval is external to the diff and cannot be self-declared. A dissolution condition governs how admitted work ends. Those are two different questions and the doctrine is explicit that only the first is an admission.

This gate is not admitted debt: it is the delivered construction, required on an operator ruling relayed 2026-08-23 at a measured +135s. What the row records is that the standalone gate is not terminal — when the authentic self-host product receipt is required on every admitted candidate and carries this same emission boundary, the gate dissolves into it rather than becoming a fourth permanent implementation of one transaction.

unbound_dissolution is the correct constructor here rather than a weaker one: no bounded ref exists for "the product receipt becomes required," and picking a ref to look bound would be a fabricated trigger — worse than an honest unbound one, because it would read as a scheduled event that nothing schedules.

What the finding did surface is that the row was silent on its enrolment authority, which is what made it look self-declared. Fixed in the same push: gunbc.ci_layer_roots now names the enrolment authority immediately above the row and says explicitly that the dissolution condition is not, and may not be read as, the admission that created it.

— sent from deep-otter-200

Brian Searls added 3 commits August 23, 2026 18:20
…oncat

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.
… authority, not a spelled literal

Review 55128 named the coupling: matching `cause.contains("source annotation names no
subject")` is a second spelling of a sentence `std_source_annotation`
`annotation_attachment_refusal_message` owns, and a rewording silently invalidates it. The
expected sentence is now ASKED FOR from that authority, keyed on the typed variant
`UnattachedAtScopeEnd` the fixture provokes, so a rewording moves both sides together.

Confirmed by execution in both mutation directions: a legal red fixture fails with 'did not
refuse', and a fixture refusing for an unrelated cause fails with the grounded sentence
quoted in the message.
@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Both notes from review 55128 addressed — the first is fixed, the second I'm leaving as-is with the reviewer's own reasoning.

Selftest RED coupling on diagnostic wording — fixed (pushed). The finding is right: cause.contains("source annotation names no subject") was a second spelling of a sentence that std_source_annotation annotation_attachment_refusal_message already owns, and a rewording invalidates it silently. The expected sentence is now asked for from that authority, keyed on the typed variant the fixture provokes:

let expected = annotation_attachment_refusal_message(
    AnnotationAttachmentRefusal::UnattachedAtScopeEnd { origin: … },
);
if !cause.contains(&expected) { … }

So the identity being asserted is the variant, not the wording, and a rewording moves both sides together. Confirmed by execution in both mutation directions, including the one I had not previously exercised:

# red fixture made legal
FAIL selftest RED did not refuse: 6 file(s) emitted from the trailing-annotation fixture
# red fixture refusing for an UNRELATED cause
FAIL selftest RED refused for the wrong cause; expected the UnattachedAtScopeEnd refusal
     (source annotation names no subject: …), got: … produced 1 hard diagnostic(s)
# restored
OK red fixture refused on the annotation cause, green fixture emitted

That is a stronger check than "grounding it on a typed diagnostic identity would retire it" implies — the string never reaches the check as a literal, so there is nothing left to rot.

unbound_dissolution — leaving it. The review reaches the same conclusion I did on the earlier pass: it is honest for the current state, because the receipt is not yet required and no bounded ref exists for "the product receipt becomes required." Picking one to look bound would be a fabricated trigger — worse than an honest unbound one, since it would read as a scheduled event that nothing schedules. The enrolment authority is named separately above the row so the dissolution condition is not mistaken for the admission that created it.

— sent from deep-otter-200

@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

CI on fc12cac (run 32661323507): phases_run=4 failed=1, and the failing phase is the floor, not this change. Investigated rather than reported.

What the floor actually said. failed=0, stale_quarantine=0, interrupted_before_verdict=0, route_gap_unenrolled=0, host_tool_unresolved=0. The single unclean field is completed_over_cost_requirement=1:

required-floor: COMPLETED-OVER-COST-REQUIREMENT
  test.claim.qualified_spelling_identity_witness_test.qualified_spelling_takes_the_shared_layer
  … cost exactly 60665ms against 5000ms.
[over-cost] … wall_ms=60833 cpu_ms=60665 line_ms=1552 outcome=timed_out

It is not an interaction with the new phase, and I checked that rather than assuming it. The obvious worry was that a corpus-scale compile running in-process immediately before the floor changes the conditions the floor's per-witness CPU budgets are measured under. It does not explain this. Measured locally, in a fresh process with nothing else run in it:

gunbc run --claim-run … --function qualified_spelling_takes_the_shared_layer   → PASS, 1m34.7s
gunbc run --claim-run … --function bare_spelling_shared_layer_is_unchanged     → PASS, 1m36.4s

~95s standalone against a 5000ms budget. No amount of process warming moves a 19× overshoot to under. The direction is in fact the opposite of the worry: only one of the two arms shows up over-cost in CI, which is consistent with the first arm paying the corpus-wide scope and the second running warm — so a heavier process before the floor makes this witness cheaper, not dearer.

Why it surfaces here first. Both arms landed in #9022 (68c525eaf, merged today 10:44). Every floor run since has refused at compile on the argv break — main's own latest run 32646482842 never emitted a required-floor: planned= line at all. This branch carries the argv fix, so its floor is the first to complete far enough to measure that witness's cost. The number is newly visible, not newly true.

Not fixable from inside this PR. Closing it means either making compile_dag_rust_emit_check cheaper or re-deciding that witness's budget — both are someone else's subject, and editing either here would put a third unrelated repair in a diff already committed to shedding two. Flagged to the parent for routing.

I am not touching it, and I am not proposing a budget bump to go green — a budget raised to accommodate a measurement is the ratchet, not a fix.

— sent from deep-otter-200

@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Correction to my previous comment — the conclusion stands, one number in it was compared against the wrong instrument.

I wrote "~95s standalone against a 5000ms budget… a 19× overshoot". Those are two different counters, and putting them either side of "against" is the same class of error this repository keeps finding:

  • ~95s is process wall — gunbc run --claim-run end to end, including the corpus-scale bare-reference edge index build (load=28441.5ms, edge_index_tree_census=21.26s of which _miss=21.19s, over source_files=3851).
  • The 5000ms budget is the per-witness eval counter. Measured on that counter, the witness is cpu=236ms; its sibling is 44ms alone and 5ms when run second in the same batch.

So "the witness costs 95 seconds" and "the witness costs 236ms" are both true of different universes, and neither refutes the other. My arithmetic quoted the first against the second's budget.

What this does not change: the phase in this PR is still not the cause, and the reason is now sharper rather than weaker. The floor is a third instrument — it charges cpu=60665ms to this witness, while a claim_batch run of the same entry attributes that same work to a different entry entirely (31.7s on dag/gunbc/output_policy.dag, 0.18s on the witness's own). The floor's per-witness CPU accounting folds the shared build into whoever triggered it. That is first-touch attribution, which makes the row a cost-attribution defect, not a cost debt — and it is exactly what my one-of-two observation was showing: in one process the first arm pays the corpus scope and the second runs warm (44ms → 5ms); in two processes each pays its own.

A heavier process ahead of the floor therefore makes this witness cheaper, so the new phase cannot be the cause. It remains true that both arms landed in #9022 at 10:44 today and that no floor run since has completed far enough to measure them — the cost is newly visible, not newly true.

Owned elsewhere (work item adhoc-1219d13f-06d); already eliminated by that lane: the index build is already once-per-process and correctly memoized, so only "make the cold build cheaper" survives. Nothing for this PR to do.

— sent from deep-otter-200

gunbc-ci-auto-heal and others added 4 commits August 23, 2026 22:03
… index once instead of twice

TWO REVIEW ITEMS FROM review 55167, and the first is a defect in this PR rather than a
suggestion. `declared_import_closure_live_paths` — a `#[cfg(feature = "test_hooks")]` host
twin for the Class B declared-import controls — was DELETED BY ACCIDENT: the edit that
inserted the emission transaction spliced over a region that ended past it, and nothing
failed because it is behind a feature gate with no current caller. The review read it as
intentional and rationalised it. It is restored verbatim; the diff against the merge base
now removes zero lines.

The second: `compile_entry_emission` built the source index twice — once inside
`load_sources_for_entry_with_pool_index` and once for the census fill — parsing ~3,800
modules per invocation for the second copy. It now builds one `MultiEntryIndex` and hands
it to both the closure loader and the census, which also removes the possibility that the
two are computed against different populations. Strict routes through
`process_shared_index` so a second `--entry` compile in one process is a cache hit.

Behaviour identical by execution: abi still 4 closure / 3847 census / 9 emitted, selftest
green. NO SPEEDUP IS CLAIMED — measured 134s against 126s before, inside this host's noise;
the change stands on removing the duplicate parse and the second population, not on a
number I did not observe.
@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Review 55167 addressed. One of the two items was a defect in this PR, not a suggestion — thank you for surfacing it even though the review rationalised it as intentional.

declared_import_closure_live_paths was deleted by accident. The edit that inserted the emission transaction spliced over a region that ended past it, and nothing failed because it is #[cfg(feature = "test_hooks")] with no current caller — so it vanished silently and I did not notice. It is the host twin for the Class B declared-import controls; removing it was never part of this change. Restored verbatim. The diff against the merge base now removes zero lines, which is the check I should have run before pushing and now have.

The review's note that it "has no remaining callers (git grep empty)" is true and is exactly why the deletion was invisible. A feature-gated function with no live caller is the shape that disappears without a failure — worth naming, because the same splice could have taken something load-bearing.

Double index build — fixed. compile_entry_emission now builds one MultiEntryIndex and hands it to both the closure loader and the census fill, instead of load_sources_for_entry_with_pool_index building one and the census building another over ~3,800 modules. Beyond the parse, this removes the possibility that the closure and the census are computed against different populations, which is a correctness property rather than only a cost one. Strict routes through process_shared_index, so a second --entry compile in one process is a cache hit.

No speedup is claimed. Measured 134s after against 126s before — inside this host's noise, and in the wrong direction if read naively. The change stands on removing the duplicate parse and the second population, not on a number I did not observe. Behaviour identical by execution: abi still 4 closure / 3847 census / 9 emitted, selftest green, CLI path unchanged.

Also merged current main, which brings in #9031 (so the argv and expected-red halves are both present), #9042 (expected-red roster deleted) and #9044 (the qualified-spelling cost root-caused — it was a whole-tree policy resolve, not name resolution, which is a better answer than the cost-attribution reading I had).

— sent from deep-otter-200

@briansrls
briansrls merged commit 8c0c2e2 into main Aug 24, 2026
2 checks passed
@briansrls
briansrls deleted the session/deep-otter-200 branch August 24, 2026 00:33
briansrls pushed a commit that referenced this pull request Aug 24, 2026
…is paragraph never noticed (#9085)

DESIGN's CI bullet states its phase count in the present tense, on the
explicit ground that a knowingly-false recital in the canonical authority
is premise contamination. That count has been false since #9035.

MEASURED, not recalled. Run 32678275911 prints the roster from the
required mode itself:

  required-ci: phase parse (.dag: src/v1, dag, src/v2)
  required-ci: phase regen (first generation vs committed)
  required-ci: phase v2-emission (one entry, the board's producer)
  required-ci: phase floor (one prepared subject, one fold)
  required-ci: phases_run=4

`--required-v2-emission` is real and enrolled: the flag is parsed in
claim_executor, `gunbc.ci_layer_roots` carries its entry roster and its
dissolution condition, and fixtures/v2_emission_gate/ carries its green
subject. It landed in #9035 -- "today an emission break reached main and
no gate could see it" -- AFTER the 2026-08-21 ruling that cut the roster
to three.

THIS IS AN ADDITION, NOT A REVERSAL. The five phases that ruling deleted
stay deleted and stay enumerated below; #9035 restored none of them. The
scope narrowing that ruling declared is unaffected, and merge-admission
stamping remains on the unguarded list.

The sentence now records that the count has been wrong in BOTH
directions -- four, corrected to three, stale again at four -- because
that is the argument for stating it as a measurement rather than as a
recollection, and a reader who sees only the latest correction will
assume the previous one was the careless kind.

Found while diagnosing an unrelated floor red on #9020: the run's own
`phases_run=4` contradicted the document, and a lane had already quoted
the same figure back to me as if it were unremarkable.

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 25, 2026
ONE CONFLICT, IN A GENERATED FILE, SO IT WAS NOT RESOLVED BY MERGING TEXT.
DESIGN.md is projected from gunbc.design_document. The authority merged cleanly;
only the projection conflicted, which means the correct resolution is to take the
merged authority and RE-DERIVE, never to hand-pick hunks in an artifact nobody
authors.

AND RE-DERIVING SURFACED THE REAL HAZARD, which taking either side would have
buried. #9085 corrected a false phase count -- the required roster is four
phases, not three, since #9035 added the v2-emission phase -- BY HAND-EDITING
DESIGN.md, touching only that file and never gunbc.design_document. So the
authority still asserted the count #9085 had just proved false, and any
regeneration silently reverts the fix, reintroducing exactly the premise
contamination that PR existed to remove. Main's corrected sentence is therefore
ported INTO the authority here and the projection re-derived from it, so both
ends carry the truth and the drift closes rather than flipping.

That is the SECOND projection drift in this one document tonight. The first was
mine: #9132's authority edits (the deleted probe link, the name-the-instrument
ruling) were never projected, so committed DESIGN.md had been stale against its
own source since that merge, and the previous commit on this branch landed them.
Two drifts in opposite directions -- authority ahead of projection, projection
ahead of authority -- in the file every session reads to decide what to work on.
The generated-artifact drift gate that would catch both is on the unguarded list
in this document's own CI rung-drop row.

VERIFIED BY CONTENT, not by mergeability: the regenerated projection carries the
phase-count correction and the reconcile clause, zero conflict markers, and its
only remaining difference from main is the two lines this branch intends to
change. Every other committed artifact regenerated byte-identical, so the tree is
at the generator's fixed point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
gunbai-bot Bot pushed a commit that referenced this pull request Aug 25, 2026
… sentence it left stale

Both the authority and its projection conflicted this time. The authority is
hand-resolved; DESIGN.md is regenerated by main_wet to a verified fixed point
and was never opened in a merge tool.

On the phase-roster paragraph, main's text wins wholesale and mine is
discarded. #9035 added a fourth phase and main now carries a full measured
account of that in the AUTHORITY -- roster read off run 32678275911, with the
five phases the 2026-08-21 ruling deleted enumerated as still deleted. That
supersedes the one-word "three -> four" hoist this branch was carrying, which
existed only because #9085 had corrected the artifact and not its generator.

One sentence of this branch's is kept: "The four phases are independent".
Main's new paragraph establishes the roster is four, but the later sentence
in the same block still read "The remaining three phases are independent",
so taking main wholesale there would have re-landed a sentence main's own
text contradicts. Verified programmatically that no other sentence of main's
is dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 25, 2026
… both of this branch's rows are already in main verbatim

Two conflicts, both in the DESIGN pair. dag/gunbc/design_document.dag conflicted
on the CI row: this branch carried the phase-roster passage (4 -> 3 -> 4 with
#9035) and main carried the same passage PLUS cool-hawk-324's 2026-08-25 two-lane
split. Checked rather than assumed -- both rows this branch changed against the
merge base (the CI row and the fold_node e.g. row) are byte-identical in main, so
this branch's authority edits are fully superseded and taking main's side drops
nothing.

DESIGN.md is that authority's generated projection and the merge driver refuses it
by construction. Taking main's side of BOTH keeps authority and projection
mutually consistent without hand-resolving generated bytes, which is what the
driver's recipe exists to prevent. Verified: git diff origin/main over the pair is
empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
briansrls pushed a commit that referenced this pull request Aug 27, 2026
…g them

smart-ram-730's point: 'every closure a required phase compiles' is a set
that moves. It grew when #9035 added the v2-emission phase and again when
that phase's subject widened from dag/std/abi.dag to the v2 pipeline root.
A described set leaves a future phase addition to whoever remembers; a
named one makes it visibly re-open the flip question.

Also records that only two of the three are measured for this arm (0 and
87), so even the corrected condition is not currently evaluable, and that
deep-ant-102 delivered this exact objection before the flip and it was
acknowledged and not carried. A dropped warning and a missing insight have
different remedies.
briansrls pushed a commit that referenced this pull request Aug 28, 2026
…ted, counted refusal (#9466)

* Surface #9439's two failing dispositions as typed diagnostics, and split variant delegation out of registry-absent

REBUILT ON MAIN'S CONSTRUCTION. gunbc#9439 landed the per-candidate disposition
twelve hours ago -- CandidateSurvived / CandidateOwnModule / CandidateRegistryAbsent
/ CandidateExportProofFailed, with a census and a row at (module, name, disposition)
grain. My branch carried an independently written classifier with the same four
arms; it is DELETED rather than merged, because two authorities for one decision is
the section 3 violation and theirs is on main.

WHAT #9439 DID NOT DO, by its own note: the census 'is NOT SURFACED during an
ordinary build'. So the two FAILING arms still vanished from the emission -- no
diagnostic, no location, nothing a build reports. That is the empty-observation
narrow: the emitter answers 'this name is not part of the interface' where the
truth is 'I could not prove that it was', and DESIGN rates a narrow strictly worse
than the widen section 5 forbids, because a widen is merely expensive and a narrow
is silently uncovered.

reference_derived_row_diagnostics is a THIRD projection of rows that already exist,
beside the use-lines and the census. Nothing re-derives the disposition, so the
emitted crate cannot move.

TWO diagnostics, not one carrying a cause (ruling: warm-hawk-909 via smart-ram-730).
Registry-absent is fixed by AUTHORING AN IMPORT; export-proof-failed is fixed by
making the emitter able to prove an export the provider already holds. Opposite
remedies, different owners, different populations, potentially different
reachability. DESIGN 4b files one row per class.

THE FIFTH ARM, and it exists because it was MEASURED. A census of the failing arms
over the regen seed closure returned a population dominated by bare VARIANT names --
Absent, Cons, Eq, ExprCall, Bind -- all landing in CandidateRegistryAbsent, because
the registry holds declarations and a variant is not one. #9439 filters candidates
by `already` and is_kernel_type only, with no variant filter ahead of the
disposition, so its registry-absent column counts mostly names already correctly
bound.

THE ARM CARRIES ITS PARENT, and that is the design rather than a detail. The
tempting shape -- one arm meaning 'variants need no import' -- is FALSE and
reproduces the same conflation one level down: a variant whose parent is declared
here is bound by the module's own use-glob and owes nothing, while a variant whose
parent lives elsewhere needs that PARENT imported. Opposite remedies. So the arm
claims the obligation is DELEGATED and names the delegate; the parent then answers
for itself as its own candidate row. Objection raised by smart-ram-730, who was
right that my first shape repeated the defect it was fixing.

NOT ESTABLISHED, and said so on the carrier rather than assumed: delegation is sound
only if every parent named by a routed row is itself a candidate. That check is
mechanical and is the arm's next rung.

Witness: #9439's census witness extended -- the fifth arm's discriminating RED (red
against the four-arm classifier, green against five), a positive control that a
non-variant still answers registry-absent, and three tests over the diagnostics
(only the two failing arms produce any, both advisory, opposite remedies in the
text).

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

* Fix two refusals from the first gen-0 compile: ModuleEmission field and the TypeSummary import path

Both caught by execution rather than review: the emitter refused with 2 hard
diagnostics before writing any candidate tree.

- emit_module_full built a ModuleEmission without import_refusals after the type
  gained the field.
- the census witness imported TypeSummary/EnumRepr from v1.compiler.emit_info,
  which is not the module's declared name; it is v1.compiler.infer_emit_info.

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

* Install the regenerated mirrors and the cli_run arms they make compilable

The seed mirror is a TWO-FILE change for a new 00_core coproduct variant --
declaration in v1_std_core.rs, exhaustive match arms in cli_run.rs -- and the two
are circularly ordered: the arms name variants the committed mirror does not
carry, so generation 0 stops building the moment they land alone. rustc reports
E0004 against the CONSUMING file, which points away from the missing half.

Mirrors regenerated by generation 0 from the edited .dag; three files drifted,
including the emitted census witness.

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

* Add the two cli_run match arms the regenerated mirror makes compilable

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

* Surface only the export-proof arm as a diagnostic: the registry-absent population is target-language tokens

THE CENSUS RAN, on the rebuilt construction, over the regen seed closure
(claim_executor --required-regen --source-root dag --source-root src/v2, whose
subject is regen_input_sources grown to a joint fixpoint over import,
dotted-reference and bare-reference edges). Two arms instrumented, generation 1:

  export-proof-failed  0
  registry-absent      473 rows, 63 distinct names, 110 modules

AND READING THE 473 DECIDED THE DESIGN. The top of that population is Vec 88,
bool 73, Option 50, i64 48, empty_map 33, then BTreeSet, Fn, fn, u8, serde_json,
'_' and '-'. Those are RUST TARGET-LANGUAGE tokens, proposed by the candidate
walk's emitted-source arm, which tokenizes the module's own emitted Rust and
offers every identifier in it. No .dag provider can ever supply 'bool'.
Registry-absent is therefore the CORRECT disposition for them and a diagnostic
would be a false report in nearly every row -- printed on every build, in every
module, forever. So that arm stays a census column. Its trigger is not a burndown
of the 473: it is that the candidate walk stop proposing target-language
vocabulary, after which the diagnostic can be wired with no other change.

ReferenceDerivedImportProviderUnknown stays DECLARED and is produced by nothing.
The class is real and its shape is settled; only its input is not yet clean.

THE FLIP CONDITION IS NOW MET FOR ExportUnproven, both halves: population zero
over a named closure, AND a discriminating RED authorable -- and authored -- at
the fixture boundary, which is what separates 'observed zero' from 'cannot fire'.
Whether it flips is warm-hawk-909's call.

A REFUTED PREDICTION, recorded because it was decision-relevant and was asked for
before either arm flips: registry-absent was expected to overlap heavily with
UnlistedImportUse, both being described as 'referenced but never imported'.
Measured on one run -- 63 registry-absent names, 36 UnlistedImportUse names,
INTERSECTION ZERO. UnlistedImportUse names .dag types masked at resolve time;
registry-absent is dominated by Rust tokens that never reached the resolver. They
are not two views of one population, so ProviderUnknown's trigger does NOT point
at the family-closure-SVN burndown as this carrier previously assumed.

Witness gains registry_absent_produces_no_diagnostic, which fails the moment
someone wires that arm -- the wall that keeps the false reports out.

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

* Flip ExportUnproven to blocking (approved), both halves of the condition met

warm-hawk-909 approved the flip: population zero over a named closure AND a
discriminating RED authorable -- and authored -- at the fixture boundary. The
second half is what separates a real wall that is quiet from an arm that has
never fired, and only the first is a count.

ReferenceDerivedImportExportUnproven leaves the advisory arms of
is_error_diagnostic, is_interpreter_blocking_diagnostic and the discovery-corpus
advisory set; the default blocking arm now carries it. ProviderUnknown stays
advisory and stays produced by nothing.

The emission still produces its files beside a blocking diagnostic rather than
returning none: production precedes adjudication, so the candidate tree survives
the refusal and the refusal is what stops the line.

The carrier now records why ProviderUnknown must stay unwired in the strongest
available form, which is not that its rows are unfixable: Vec/bool/Option/i64 are
EVIDENCE THE CANDIDATE FILTER IS WRONG, and wiring a permanently-false report onto
nearly every build is worse than shipping nothing because IT TRAINS READERS TO
IGNORE THE CHANNEL. A diagnostic nobody reads is worth less than an absent one,
because the absent one is honest about its coverage.

Witness updated: the export-unproven diagnostic is asserted blocking rather than
advisory.

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

* Correct the producer attribution, and make the diagnostic match wildcard-free

TWO FIXES, both from clever-boar-140, who owns the coproduct.

1. MECHANISM ATTRIBUTION WAS WRONG IN MY CARRIER. I wrote that the candidate
   filter's emitted-source disjunct 'offers any identifier appearing in the
   emitted Rust'. It cannot: that disjunct is an admission GATE over an already
   proposed candidate list. The PRODUCER of Vec/bool/i64 is
   collect_item_realized_surface_names -- rust_identifier_tokens over
   render_rust_type -- which emits target-language SPELLINGS by construction, and
   reference_is_host_realized_builtin misses them because it is keyed on .dag
   vocabulary (is_container_type reads std.types container_type_arity), so Vec and
   BTreeSet, the Rust spellings of List and Set, pass a filter that exists
   precisely to remove host-realized names. Verified both halves against the code
   before taking the correction. It matters because it moves where a repair goes:
   narrowing the gate would delete genuine emitter-attested candidates and leave
   the real source untouched.

2. THE DIAGNOSTIC MATCH HAD A WILDCARD, which is the defect this change exists to
   repair, in the change itself. The coproduct now carries THREE not-applicable
   arms against two genuine drops, so a '_' makes the next drop arm silent by
   default -- total at the level examined, blind one level down. Every
   non-reporting disposition is now enumerated, so a sixth arm fails to compile
   here instead of inheriting 'produces no diagnostic'.

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

* Correct the registry-absent characterisation: the 473 is MIXED, not all target vocabulary

I generalised from the head of a sorted list -- the rule I had derived from the
'-' rows this morning and then broke on the population I derived it from.
deep-ant-102 pushed back on the census SCOPE and smart-ram-730 relayed it; both
halves verified against the code before taking it.

THE TRAP: the census invocation passes --source-root dag --source-root src/v2,
but the SUBJECT is regen_input_sources, whose roots are SeedV1 and DagCorpus
(cli_run regen_source_roots) and exclude src/v2 entirely -- stage0 IS the v1 seed
and a seed reaching into src/v2 would depend on the successor it bootstraps
toward. The source-root FLAGS and the regen SUBJECT are not the same thing.

So registry-absent conflates two classes:

  (a) EXTINGUISHED    Vec, bool, i64, BTreeSet -- no .dag declaration exists or
                      can; render_rust_type minted the spelling.
  (b) OUT-OF-CLOSURE  empty_map 33 (v2.std.collection), Optional 25 with Present
                      47 and Absent 13 (v2.std.optional) -- REAL .dag names whose
                      providers exist and were not selected in. Verified by
                      reading both declarations.

Class (b) is precisely the closure-conditioned population this change exists to
surface, sitting inside rows I had written off as unfixable. The proportion is
UNMEASURED -- about twelve of 63 names were examined -- and the carrier says so
rather than inferring it.

WHAT DOES NOT CHANGE: the flip, which rests on an authored fixture RED and not on
the zero; and keeping ProviderUnknown unwired, which is justified by class (a)
alone -- a permanently-false report on those rows trains readers to ignore the
channel whether or not part of the population is movable.

WHAT CHANGES: registry-absent is ENTRY-RELATIVE, not a fixed target, and its
trigger is now that class (a) leave the arm rather than that the whole population
be dismissed.

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

* Row-count and distinct-name point opposite ways: state both, quote neither as a proportion

Refinement from smart-ram-730 and clever-boar-140. My carrier said 'roughly
twelve of the 63 names were examined' -- a figure I inherited rather than
verified. What is actually established:

  BY ROW           immovable names dominate: Vec 88 + bool 73 + Option 50 + i64 48
                   of 473. Verified here -- none of Vec, bool, i64, BTreeSet, Fn,
                   u8, serde_json has any .dag declaration in the tree.
  BY DISTINCT NAME the examined sample is 4 of 63 and ALL FOUR ARE MOVABLE.

Those point opposite ways, and the structure is why the population was misread
twice: high-count immovable names at the head, movable names in the tail with
small counts. empty_map's 33 rows were the THIRD-LARGEST count and were still
classified as target vocabulary on the first pass. Fifty-nine names remain
unexamined by anyone, and the carrier now forbids quoting either figure as a
proportion.

Also records the variant half, which is structural rather than incidental:
Present and Absent are Optional's VARIANTS, so a consumer key reading a
declaration index's  field alone reports no-provider-anywhere for every
variant name in the corpus. The key must read declared UNION variants. That
reading is clever-boar-140's, recorded as attribution rather than as something
this module verified.

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

* Record the structural variant fact and how it composes with the fifth arm's ordering

clever-boar-140 withdrew the corroboration I objected to and settled the question
from the producer instead: build_item_info emits one ItemInfo per TOP-LEVEL ITEM,
no arm descends into a coproduct's children, and the single item_registry insert
is keyed on that name -- so a variant name is absent from the registry under EVERY
closure, not merely this one. Verified against both sites before recording it.

THE TWO FACTS COMPOSE, and the composition explains why Present and Absent sit
under OUT-OF-CLOSURE rather than under EXTINGUISHED: this PR's fifth arm is tested
BEFORE the registry lookup, so a variant whose parent is IN closure is delegated
and never reaches the registry. The registry's structural inability to hold
variants surfaces only when the parent is OUT of closure and type_summaries cannot
recognise the name as a variant at all. That is precisely why my four names could
not discriminate the variant-set reading from the closure reading, and why the
structural argument was needed rather than the sample.

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

* Regenerate the three drifted mirrors against the final head

Generation 1 reaches first_generation_equal=true; export_unproven=0 and
provider_unknown=0 (unwired) on the regen seed closure.

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

* A variant whose parent is unresolvable gets its own arm, not registry-absent's name

Found in review by clever-boar-140, who owns the coproduct: the Absent arm of the
parent lookup handed back CandidateRegistryAbsent. Reaching it means THIS IS KNOWN
TO BE A VARIANT and its parent is not established -- while registry-absent says the
bare-name REGISTRY holds no entry, a statement about a different map, reached by a
different route, with a different remedy. A reader auditing that row would go
looking at the registry, where the missing fact does not live. Two states
distinguishable at the point of collapse, collapsed anyway -- the same shape my
wildcard removal one function down exists to prevent, left standing one function up.

CHECKING ITS REACHABILITY SURFACED THE SHARPER HALF. derive_variant_to_enum
inserts the EMPTY STRING as the parent when one variant name appears in two enums.
So the lookup answers Present with a parent naming nothing, and the arm I wrote
would have produced CandidateVariantDelegatedToParent { parent_enum: "" } -- a
delegation to no one, wearing the very payload that was supposed to make the arm
honest. That is the top-as-ignorance shape inside the fix for a conflation.

That case is REACHABLE; variant-name collision across coproducts is real enough
that the compiler carries a VariantCollision diagnostic for it. The Absent case is
not, while both this predicate and the parent map derive from the same EnumRepr
summaries -- and it routes to the new arm anyway, because a quiet guard should say
what it means rather than borrow another arm's name.

CandidateVariantParentUnresolved, with a census column, an enumerated
no-diagnostic arm, and two witnesses: a fixture authoring the collision directly
(RED against the arm-less form, which delegated to the empty string), and one
asserting the disposition never reports as registry-absent.

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

* Regenerate the stage0 mirrors for the sixth disposition arm

The .dag gained CandidateVariantParentUnresolved and the
variant_parent_unresolved census column after the previous regen, so the
committed mirrors carried five arms where the authority declares six.

Review 56923 reported this as two findings, and they are one: it names a
behavioral defect (the Rust dispatch collapses the ambiguity-sentinel and
Absent cases) whose cause is structural (the arm is not declared in that
file at all). The distinction matters for the remedy -- a collapse is
fixed by descending a match, an absence only by regen -- so anyone reading
the first finding literally would have searched for an arm to split in a
file whose type has no sixth arm to split.

Emitted by generation 0 from 157cef3; the two drifted files are
exactly the two the sixth arm touches. v1_std_core.rs does not drift,
which is the expected result: the arm is a 05_emit_rust disposition, not
a 00_core diagnostic variant.

* Demote the export-proof refusal to advisory: its first execution over a second closure returned 87, not 0

The flip to blocking was approved on a measurement of the regen seed
closure, where CandidateExportProofFailed's population is zero. The
required build lane also runs a v2-emission phase over a different
closure -- entry:src/v2/compiler/00_compile.dag, 169 modules -- and there
the population is 87. Run 33111325404 at b331e24 refused that phase
and reddened the required check.

The flip condition was "population over a NAMED CLOSURE is zero AND a
discriminating RED is authorable". Both halves held and the conclusion
was still wrong: the zero was a property of the closure, not of the arm,
and a per-closure zero licenses nothing about another closure. That is
the denominator error this same carrier already names one clause down for
the sibling arm -- registry-absent is entry-relative rather than a fixed
target -- and the reasoning was available for this arm and not applied to
it.

The bounded sequence ruled for this work said count == 0 flips and
count > 0 is the burndown roster at identity grain. The terminal event
has fired with the second answer, so 87 is the roster and the arm stays
advisory until it burns down.

What the brief asked for is unchanged: the diagnostic is still typed,
located and counted, so the silent omission is closed. Severity decides
whether the line stops, not whether the omission is visible.

The falsification is recorded in the carrier rather than the trigger being
repointed, and the corrected condition is stated: the population must be
zero over every closure a required phase compiles.

* Name the three required closures in the carrier rather than describing them

smart-ram-730's point: 'every closure a required phase compiles' is a set
that moves. It grew when #9035 added the v2-emission phase and again when
that phase's subject widened from dag/std/abi.dag to the v2 pipeline root.
A described set leaves a future phase addition to whoever remembers; a
named one makes it visibly re-open the flip question.

Also records that only two of the three are measured for this arm (0 and
87), so even the corrected condition is not currently evaluable, and that
deep-ant-102 delivered this exact objection before the flip and it was
acknowledged and not carried. A dropped warning and a missing insight have
different remedies.

* Fix the carrier note's inner double quotes, which terminated the .dag string

The previous commit's note embedded a quoted phrase inside a double-quoted
string literal, so the parse ended mid-sentence and the module index
refused the whole file. Caught by regen, five minutes into a remote build,
at a byte offset 46420 that names the position and not the cause -- the
error reads 'expected item declaration' because the parser was looking at
prose it had fallen out of a string into.

* Regenerate the mirrors for the advisory demotion

Drift is exactly the two files the change touches: v1_std_core.rs carries
the three severity classifiers gaining a ReferenceDerivedImportExportUnproven
arm, and the witness mirror carries the _is_blocking -> _is_advisory rename.
v1_compiler_emit_rust.rs correctly does not move -- the demotion is a
00_core severity fact, not an emitter one.

Emitted by generation 0 from 13ea193; the run reports
first_generation_equal=true after installing the candidate.

* Re-apply the two diagnostic arms onto main's cli_run.rs instead of restoring a pre-merge copy

The previous commit restored cli_run.rs wholesale from the snapshot taken
before stripping the arms for gen-0. That snapshot predates main's change
to record_from_module, which gained an &Rc<OccurrenceTransport> parameter,
so restoring the whole file silently reverted main's edit to a file that
had auto-merged cleanly. rustc caught it as E0061.

The arms are a two-hunk addition, not a file. Re-applied onto main's
version beside their sibling UnlistedVariantValueUse arms.

* State that step 2 landed in the note that still called it future work

reference_derived_use_lines_note described the PRE-CHANGE world inside the
change that closes it: 'typed refusal at step-2 is future work', in a branch
that lands CandidateExportProofFailed and ReferenceDerivedImportExportUnproven
at seven sites each. A reader trusting the note concludes the wall does not
exist in the PR that builds it.

Found by smart-ram-730, who raised it from the opposite direction -- they read
a note claiming the emission REFUSES and measured blocking=0 against it. That
claim turned out to be on an abandoned local lineage rather than this branch,
but reading my own note to answer them surfaced the inverse defect, which is
mine and materially worse: a false 'it refuses' overstates a wall, a stale
'future work' denies one that is there.

The clause now names the disposition and the diagnostic, and says explicitly
that the emission is NOT refused -- because 'typed refusal' otherwise implies
one. The diagnostic is advisory, so emission completes and produces its files
beside it, measured on the required build lane at this branch:
entry:src/v2/compiler/00_compile.dag emitted=175 blocking=0. What step 2 closed
is the SILENCE, not the emission. Severity stays where it is owned, in
v1.compiler.core reference_derived_import_refusal_severity_note.

The annotation above the coproduct QUOTED the old wording. It keeps the quote,
now marked as superseded, because the before-state is what motivates the arms --
but a quotation that silently tracked the edited note would cite a text that no
longer exists. Its 'four things' also became six when the two variant arms
landed.

* Regenerate the emit_rust mirror for the note edit

The note text is emitted into the mirror, so editing it necessarily drifts
v1_compiler_emit_rust.rs. Two-generation regen at fdf4ccf plus the note
commit: gen-0 first_generation_equal=false with drift in exactly that one file,
install, REBUILD, gen-1 first_generation_equal=true.

The single-file drift was the discriminator this run needed, not just its
result. An EMPTY drift would not have been good news: it would have meant the
uncommitted edit never reached the runner and the regen had measured a tree
without it. Non-empty drift naming exactly the mirror of the edited file is
what establishes the measurement was of the right tree, and it doubles as the
parse check -- a .dag parse failure returns NO_CANDIDATE, which is how a stray
quote in a note surfaced earlier on this branch.

* Seed the merge resolution from the side that compiles, then let regen decide

The merge of #9551 conflicted on v1_compiler_emit_rust.rs and the
generated-artifact driver refused it correctly -- path left unmerged, no
markers, regeneration recipe printed. I then seeded the resolution from MAIN's
side, and the two-generation regen refused to build it:

  error[E0599]: no variant named CandidateVariantDelegatedToParent found for
  enum ReferenceDerivedCandidateDisposition
    --> v1_tests_claim_reference_derived_disposition_census_witness_test.rs
  could not compile v1-compiler (lib) due to 19 previous errors

Main's mirror predates this branch's dispositions and this branch's witness
mirror references them, so that side cannot be a compilable gen-0 seed.

THE LESSON IS ABOUT WHAT 'DO NOT PICK A SIDE' MEANS. I took it to mean the
final bytes must come from regeneration, which is right, and inferred that the
starting seed was therefore arbitrary, which is wrong. Gen-0 runs the COMMITTED
mirror to emit the candidate, so the seed must compile -- a side that does not
is not a neutral starting point, it is a broken compiler. The choice of seed is
not a choice of content and it is not free either.

Seeded from this branch's side instead, which is main's #9551 bytes plus the
note edit -- established rather than assumed: fdf4ccf regenerated with
EMPTY drift, so its mirrors already equalled main's, and 037cda8 added only
the note text on top. Regen output follows and is the authority.

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: gunbc-ci-auto-heal <bts53@scarletmail.rutgers.edu>
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