Repository navigation
Design a required phase that compiles one v2 entry: today an emission break reached main and no gate could see it - #9035
Conversation
`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.
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.
…ix/argv-command-ls-seal
…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.
|
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 On 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.
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: — sent from deep-otter-200 |
…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.
…ession/deep-otter-200
… 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.
|
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: 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: 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.
— sent from deep-otter-200 |
|
CI on What the floor actually said. 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: ~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 ( Not fixable from inside this PR. Closing it means either making 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 |
|
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:
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 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 — sent from deep-otter-200 |
… 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.
…sion/deep-otter-200
|
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.
The review's note that it "has no remaining callers ( Double index build — fixed. 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 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 |
…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>
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
… 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>
… 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
…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.
…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>
What escaped
On 2026-08-23
gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/03_ingest.dagrefused 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.dagparse 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 underdag/test/manual/— which no required phase parses either. Two independent misses, one artifact.The measurement that decides the design
An
--entrycompile 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 ofdag+src/v2whichever entry is named. Measured locally on this branch (both of the day's fixes resolved in — see Baseline below):dag/std/abi.dagsrc/v2/compiler/03_ingest.dagAnd with the real specimen planted back into
dag/test/manual/, the small entry refuses, naming the file and byte range: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.dagrow (gunbc.ci_layer_rootsrequired_v2_emission_entries, aList<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_compilestage0_self_compile_refusal_message, the same authoritygunbc compilealready 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 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_runnercallsstat_owner_user_name_commandwith no import ofextdeps.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:dag/gunbc/fleet_converge_cli.daggunbc.command_runnerfunction 'stat_owner_user_name_command' not found in scopeA peer isolated the variable: adding one import of
gunbc.host_effect_realize(which does importextdeps.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:
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
32664434197carries 59 distinct unresolved symbols across 137no such functionoccurrences (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 onrequired_v2_emission_entriesand a cost decision — 03_ingest's closure costs +214s.It does subsume a whole-tree
.dagparse sweep ofdag+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.dagon currentorigin/mainstill ends with a standalone//block and no declaration after it — the original 2026-08-23 specimen. #9027 moves thetest fnback under that block and is still open.Substituting only that file's main content into an otherwise-passing tree and running the phase:
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 underfixtures/v2_emission_gate/, differing in one thing.red/subject.dagcarries the real specimen (trailing block, no following declaration);green/subject.daghas 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:
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 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_runcompile_entry_emission, and both consumers call it:gunbc compile --entry <M> --target rust— the board's producer, invoked bydocs/probes/curated_cargo_probe_one.sh, whoseEMIT_REFUSEverdict 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_compilestage0_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 treegunbc compilerefuses.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 thecompiled:line, 0 blocking, 503 advisory, exit 0 (176 byfind -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 asemitted=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.EntryEmissionDispositionis nowCompleted { emitted_count }/Refused { phase, cause }/NotExecuted { earlier_phase, cause }. The tag is on the counts line, and the count fields readn/a— deliberately not0— on any path that never ran. All three arms confirmed by execution:The invariant is that emission COMPLETED — never a file count. A legitimate compiler change may alter a closure's size, so
emitted == 177is a change detector wearing an invariant's clothes.Enrolment, and what it is not credited for
Enrolled as
--required-ciphase 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_executorprocess 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_rootsrequired_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(head6570ec5) —phases_run=4 failed=1, the single failure beingfloor refused: ExpectedRedIdentityDidNotExecute count=3on the threecompile_accepted_unevaluable_program_controlidentities. 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.origin/fix/argv-command-ls-seal@a47aded, whosefloor_expected_red_is_livenow 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.