Skip to content

The emit-compile fault's subject is the entry's OWN emitted module, and its absence is a typed refusal - #9441

Closed
gunbai-bot[bot] wants to merge 1 commit into
session/deep-gull-307from
session/vivid-badger-113-work
Closed

gunbai-bot[bot] wants to merge 1 commit into
session/deep-gull-307from
session/vivid-badger-113-work

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Follow-up to #9405 (stacked on session/deep-gull-307 — retarget to main once that merges).

The defect

The emit-compile phase's mutation machinery is sound: one fault, one file, must fail alone, byte-exact restore, green back. The subject selector was not. mutation_subject took the first declared module where m != entry_module — which is a shared-core member in every closure that has one.

Measured over the whole roster (run 33055948820, all 8 rostered entries):

  • 7 × std_error_primitives
  • 1 × v1_rt — the emitted runtime, present in every closure

So 8 of 8 entries mutated a shared-core module. Every arm was honestly Discriminated; there was no missing arm to notice. The verdict established cargo ran and refused when the shared core was broken, never this entry's own closure is what was compiled. The corpus name for this is total at the level examined, blind one level down.

Why it matters concretely: the entry's own module is the one member nothing else references — the others are its dependencies, and a dependency does not import the root. So it is exactly what a faulty emission could drop while the crate still compiled. A partial drop that breaks a reference is already caught by the baseline; the uncaught case is a reference-closed drop, and the entry's own leaf is the canonical one.

The fix

MutationSubject is now a struct with a private field whose only constructor derives the module from the entry. "A shared member carried the fault" is unwritable here rather than merely unselected — construction over validation (§5), not a check that could be relaxed.

Absence is MutationVerdict::SubjectRefused carrying a typed MutationSubjectRefusal that names the entry. There is no fallback arm, because a fallback accepts exactly the bad case: it would hand the phase a Discriminated verdict computed over precisely the tree that is broken. SubjectRefused fails the entry like every other non-Discriminated arm.

The refusal's three arms are kept apart because their owners differ, not for completeness:

arm owner remedy
ClosureManifestUnreadable probe crate filesystem / probe root
EntryModuleNotDeclared the emitter the closure lost its own root
EntryModuleFileMissing the write stage declared, never written

closure_modules stops rendering an unreadable lib.rs as an empty vector for the same reason — that reading reports the emission arm for a filesystem fault (execution-provenance loss).

The log still says which kind was mutated (subject=EntryOwnModule module=…) rather than leaving it to be inferred.

What was preserved

Untouched: one fault in one file failing alone; the baseline above it and the byte-exact restore below it as the two controls; the red required to name the injected symbol; every non-discriminating arm stopping the line with its own typed cause; and RestoreFailed adjudicated before every fault verdict — the source-order test that pins that ordering still passes. SubjectRefused returns before the fault is injected, so there is nothing to restore on that path.

Reachability, stated honestly

Measured 8/8 entries emit their own module as its own .rs, so the refusal arm is reachable-but-empty by design — a healthy quiet guard, not a decoration. It is not a check whose RED is unauthorable: every arm is authored directly at the fixture boundary and executes.

Two wrong turns the measurement already killed

  1. "lib.rs dependency order puts the shared core first." False — the entry is second-to-last in 6 of 8 but first in 2 of 8 (std_abi, std_logic). Order was never the mechanism; the != entry_module filter was, and it stepped over the entry explicitly wherever it sat. Both shapes are pinned as separate tests.
  2. "Just mutate the LAST module." Catastrophic — v1_rt is last in all 8. That rule picks the emitted runtime every time: strictly worse than what it replaced.

Carriers

  • gunbc.ci_layer_roots — the subject rule stated as policy beside the roster it governs.
  • gunbc.emitted_closure_compile_seed_growth — declaration rows updated: MutationSubjectRefusal and mutation_subject_refusal_summary added, the two retired tests replaced by the six new ones, and two tests that were never enrolled (the_probe_root_name_is_composed_in_exactly_one_place, a_failed_restore_is_not_masked_by_a_non_terminal_fault_verdict) added.

Also folds in one held citation fix from #9405: the features comment cited stage0_partition_row_features where the code calls stage0_features_for_crate_kind.

… with absence a typed refusal

The mutation machinery was sound; the SUBJECT SELECTOR was not. It took the first
declared module that was not the entry, which is a shared-core member in every
closure that has one. Measured over the whole roster: 8 of 8 entries mutated a
shared member -- 7 x std_error_primitives, 1 x v1_rt, the emitted runtime, which
is in every closure. Every arm was honestly Discriminated and there was no
missing arm to notice, so the phase established "cargo refused when the shared
core was broken" while reading as "this entry's closure is what was compiled":
total at the level examined, blind one level down.

The entry's own module is the one member NOTHING ELSE REFERENCES -- the others
are its dependencies, and a dependency does not import the root -- so it is
exactly the file a faulty emission can drop while the crate still compiles. A
drop that breaks a reference is already caught by the baseline; the uncaught
case is a REFERENCE-CLOSED drop, and the entry's own leaf is the canonical one.

MutationSubject is now a struct whose only constructor derives the module from
the entry, so "a shared member carried the fault" is unwritable rather than
merely unselected. Absence is MutationVerdict::SubjectRefused carrying a typed
MutationSubjectRefusal that names the entry -- never a fallback, because falling
back accepts precisely the bad case. Its three arms are kept apart because their
owners differ: an unreadable manifest is a probe-crate defect, an undeclared
entry module is an EMISSION defect, a declared module with no file is a WRITE
defect. closure_modules stops rendering an unreadable lib.rs as an empty closure
for the same reason -- that reading reports the emission arm for a filesystem
fault.

Measured 8/8 present, so the refusal arm is reachable-but-empty by design: a
healthy quiet guard, with its RED authorable and authored at the fixture
boundary.

Also folds in one held citation fix: the features comment cited
stage0_partition_row_features where the code calls stage0_features_for_crate_kind.
@gunbai-bot

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #9442, which the dashboard opened on the same head branch and tracks for node completion. Same commit; no content difference.

@gunbai-bot gunbai-bot Bot closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants