Skip to content

The v2 production door was not refusing multi-declaration modules; it was silently truncating them - #10907

Merged
briansrls merged 4 commits into
mainfrom
session/sharp-ibex-816
Sep 10, 2026
Merged

briansrls merged 4 commits into
mainfrom
session/sharp-ibex-816

Conversation

@briansrls

@briansrls briansrls commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

What the brief expected, and what execution found

The brief's first cut was to remove generate_rust_module_emission_candidate's exactly-one-declaration ceiling, on the reading that it "removes the admission wall excluding almost every real corpus module", and to leave the collector alone "unless execution reveals a real defect in it". Execution revealed one, and it changes what the cut is.

v2.compiler.emit_produced produced_decl_conjs_in_tree asked whether a node was a Conj whose first child is a Named edge into an Arrow, and stopped descending there. A resolved .dag module carries one Named-into-Arrow edge per declaration on a single shell Conj — so the shell satisfied that predicate. The walk recorded the shell as one declaration, and emit_produced_decl, which reads children[0], emitted declaration 1 and dropped 2..N while reporting Accepted.

Measured through observe_provenanced_rust_emission_from_ingest over an ingested two-declaration module, before the repair: ArtifactProduced, carrying exactly the first declaration.

Two consequences for the brief's plan:

  • The rust_module_emission_decl_ambiguous surplus arm was unreachable from any real ingested module — the collector never produced a surplus to refuse. It read as a wall and was a decoration (DESIGN §4b: ask whether the check's RED is authorable before writing it).
  • The door was therefore not refusing multi-declaration modules; it was silently truncating them, and the resulting partial module is still syntactically valid and still compiles. Relaxing the ceiling alone would have widened that truncation under a wider admission, not opened a door. This is DESIGN §5's forbidden case — a fabricated plausible output — not a rung.

At N=1 the shell and the declaration are the same node, which is why the one-declaration door was correct by coincidence and every byte-level golden agreed.

The repair

The predicate moves onto the edge that carries the role: a declaration IS the named edge into an arrow, and no per-declaration node exists in the tree to collect. The walk is edge-driven, total, records one declaration per declaration edge in source order, and never descends into one. The single-edge Conj it projects per declaration is the declaration's canonical node form — the shape ingested_add_decl_node already builds — so emit_produced_decl and produced_decl_subject_from_decl keep consuming a Node and every existing consumer is untouched.

Above it, the composition keeps its typed zero-declaration refusal and sends every nonempty population through the one module authority, emit_produced_module.

Evidence (green by execution)

v2.test.claim.self_host.rust_module_emission_population — every specimen is a real ingested .dag module walked by the production observation, never a planted decl node:

arm what it discriminates
..._collects_two / ..._collects_one the collector itself, one stage before emission, so a correct concatenation cannot stand in for a correct collection. Read 1 before the repair.
..._emits_both_in_source_order both declarations, expected text composed from each module emitted alone — no transcribed copy of the emitter's own output
..._is_not_the_swapped_order same declarations, same target: only order distinguishes it
..._declarationless_module_refuses_at_emission the typed zero-declaration refusal survives the ceiling's removal
..._refuses_whole_on_unwired_target no partial module may publish — module-grain fail-closed
..._produces_on_wired_target positive control for the arm above
..._one_declaration_module_emits_terminated_declaration pins the module convention

Why the pre-existing witnesses stayed green through the whole silent-drop period, and why the specimens here are ingested modules: each one handed the fold a declaration list it had built itself (ingested_add_decl_node, table_fixture_add_decl_node). A fixture that constructs the population cannot discriminate a defect in deriving it, however thoroughly it pins ordering, separators and refusal propagation.

No regression: direct_rust_door_production_group_closing_expectation_holds, produced_decl_module_folds_declarations_in_order, produced_decl_unwired_target_still_refuses, produced_module_two_distinct_fns_assemble all re-run green.

The one byte-level change

direct_rust_door_expected_source gains the module authority's declared separator, composed from rust_source_text and emit_module_decl_separator rather than transcribed. A one-declaration module is still a module, and emit_produced_module terminates every declaration including the last, exactly as emit_module does.

Second commit: the floor's per-claim ceiling

Required-floor run 34392489718 refused seven of the eight arms INTERRUPTED-BEFORE-VERDICT at cpu_at_least=501-507ms against the lane's 500ms per-claim CPU ceiling; the floor was otherwise clean (planned=3583 executed=3583 claims_failed=0 unexpected_failures=0), and the aggregate witnesses failure is that lane result being read.

Each arm is one full production walk over one specimen, and two arms walked three specimens to compose their expected text — the walk is what the arms assert over, so it cannot be rewritten away. The repair is §2's: one computation serving several demands is carried once at their shared ancestor, via the mechanism v2.workflow.floor_pure_producer_share already models — nullary pure producers, warm-enrolled, forced at strict preparation outside every per-claim budget. The producers are the production observation; their cost is paid in full, only once each.

Two things stated on the carrier rather than left to inference:

  • Three of the seven producers serve exactly one claim and would fail this roster's cross-claim sharing conjunct. They are enrolled on its other documented ground — dag_prepared_grammar's fill-that-cannot-land: a fill costing more than one claim's budget can never complete from an in-fold first touch, so sharing is not the question.
  • This enrollment is not locally verified, and the carrier says so. The warm path runs at strict preparation, a required-floor step that a plain claim_batch --entry run does not execute — measured, the arms' claim_batch costs are unchanged at 341-1545ms after the refactor, which is what you should see when the fill is not being forced. The deciding instrument is the floor's own [floor-shared-fill] ledger: these fills must land with disposition=Stored and the seven former INTERRUPTED arms must serve hits. All eight arms remain green on their verdicts — the open question is cost, not correctness.

Filed and left open

  • Class filed: gunbc.recurring_failure_mode.shape_predicate_reads_only_the_head_so_a_container_passes_as_its_element (rung after repair: 2; ceiling 4; trigger is an absent language capability, at capability grain).
  • Standing §3 fork, deliberately not closed here and noted in the source: v2.workflow.realization_attempt collect_arrows answers this same question for itself and answered it correctly per-edge — the static field tell for this class. Consolidating it walks a resolved tree and searches for the first arrow below an edge rather than requiring the edge to target one; merging without measuring that difference would trade a silent drop for a silent empty population.
  • Not touched: the production regen selector, v2_native_route / workflow / CI routing, roadmap acceptance, stage0 target profile, native bootstrap or crate partitioning.

Scope

This does not complete the v2-emitter-production-compatible-corpus-module roadmap node. It repairs the collector prerequisite that node stands on. The remaining vertical — the stage0 census under stage0_production_target, selecting the smallest first-refusal set, driving one real module to compile in a stage0-shaped crate — needs #10886's target profile, and is worth re-running against this head: any pre-#10907 Accepted for a multi-declaration module was reporting success for a module the collector had mostly discarded.

Measured en route, not pursued: a two-declaration module whose second declaration calls the first reaches ArtifactProduced, and so does one whose second declaration calls an unresolved name — the latter is worth a look by whoever owns name resolution.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QbNFKK8mf6wBeVaCr22iNV

… was silently truncating them

The lane's premise was that generate_rust_module_emission_candidate's
exactly-one-declaration ceiling was the admission wall keeping real corpus
modules out, and that removing it meant routing every nonempty population
through emit_produced_module. Execution says the ceiling was never the wall.

produced_decl_conjs_in_tree asked whether a node was a Conj whose FIRST child is
a Named edge into an Arrow, and stopped descending there. A resolved .dag module
carries one Named-into-Arrow edge PER DECLARATION on a single shell Conj, so the
SHELL satisfied that predicate: the walk recorded it as one declaration, and
emit_produced_decl -- which reads children[0] -- emitted declaration 1 and
dropped 2..N while reporting Accepted. Measured through
observe_provenanced_rust_emission_from_ingest on a two-declaration module:
ArtifactProduced, carrying exactly the first declaration. The
rust_module_emission_decl_ambiguous surplus arm above it was therefore
unreachable from any real module -- the collector never produced a surplus to
refuse -- so relaxing the ceiling alone would have widened a silent truncation,
not opened a door. This is DESIGN section 5's forbidden case, not a rung.

The repair moves the predicate onto the edge that carries the role: a
declaration IS the named edge into an arrow, and no per-declaration node exists
in the tree to collect. The walk is edge-driven, records one declaration per
declaration edge in source order, and never descends into one. The composition
above keeps its typed zero-declaration refusal and sends every nonempty
population through emit_produced_module.

Evidence, green by execution over REAL INGESTED modules rather than planted decl
nodes (v2.test.claim.self_host.rust_module_emission_population): the count arm
reads 2 for a two-declaration module and 1 for one, measured one stage before
emission; both declarations emit in source order and the swapped order is
refused; a declarationless module still refuses at emission; a two-declaration
module against an unwired target refuses WHOLE, with the same module on a wired
target as its positive control. Every existing witness over this fold stayed
green through the entire silent-drop period because each handed the fold a
declaration list it had built itself -- which is why the specimens here are
ingested modules.

direct_rust_door_expected_source gains the module authority's declared
separator, composed from rust_source_text and emit_module_decl_separator rather
than transcribed: a one-declaration module is still a module, and
emit_produced_module terminates every declaration including the last, exactly as
emit_module does. The direct-door production group closing expectation, the
two-target module fold and the produced-module golden all remain green.

Class filed at
gunbc.recurring_failure_mode.shape_predicate_reads_only_the_head_so_a_container_passes_as_its_element.

No production regen selector, workflow/CI routing, roadmap acceptance or native
bootstrap touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbNFKK8mf6wBeVaCr22iNV
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 9, 2026 19:00
…ceiling can reach a verdict

required-witnesses-floor run 34392489718 refused seven of the eight new arms
INTERRUPTED-BEFORE-VERDICT at cpu_at_least=501-507ms against the lane's 500ms
per-claim CPU ceiling. The eighth, the declarationless refusal, completed at
~341ms -- it is the one specimen whose walk stops at the collector's empty
population.

The cost is not incidental setup that could be rewritten away: each arm is one
ingest -> assemble -> infer -> collect -> emit production walk over one specimen
module, and that walk IS what the arms assert over. Two arms walked three
specimens to compose their expected text from the single-declaration emissions.
So the repair is DESIGN section 2's -- one computation serving several demands is
carried once at their shared ancestor rather than recomputed per demand -- using
the mechanism v2.workflow.floor_pure_producer_share already models: nullary pure
producers, warm-enrolled, forced during strict preparation OUTSIDE every
per-claim budget, with each claim serving the landed fill.

The production walk is not shortcut by this. The producers ARE the production
observation, one per specimen, and their cost is paid in full; what changes is
how many times it is paid. The share points are String and Bool, fully portable.

Grounds for enrollment stated on the carrier, because three of the seven serve
exactly one claim and would fail this roster's cross-claim sharing conjunct if
read against it: these earn their rows on the FILL-THAT-CANNOT-LAND ground the
roster already records for dag_prepared_grammar -- a fill costing more than one
claim's budget can never complete from an in-fold first touch, and sharing is not
the question.

NOT verified locally, and the carrier says so: the warm path runs at strict
preparation, which is the required-floor recipe's step and not a plain
claim_batch --entry run. Measured after the refactor, the arms' claim_batch costs
are unchanged at 341-1545ms, exactly as expected when the fill is not being
forced. The deciding instrument is the required floor's own [floor-shared-fill]
ledger: these fills must land at preparation with disposition=Stored and the
seven former INTERRUPTED arms must serve hits. All eight arms remain green by
execution on their verdicts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbNFKK8mf6wBeVaCr22iNV
@gunbai-bot gunbai-bot Bot changed the title v2-emitter-production-compatible-corpus-module The v2 production door was not refusing multi-declaration modules; it was silently truncating them Sep 9, 2026
Brian Searls and others added 2 commits September 10, 2026 00:15
#10886 landed the stage0 target profile and enrolled two of its own emissions on
floor_cross_claim_pure_producers_warm for exactly the reason this branch enrolled
seven: a ~700ms nullary fill cannot complete inside a 500ms claim. Both row sets
and both carrier notes are kept -- the conflict was additive on both sides, with
no disagreement about the mechanism.

Worth recording, because it independently confirms a call made on this branch:
#10886's note states that enrolling a producer whose only callers declare
v2.test.long. would buy a preparation-time fill no planned claim reads, and that
homing its claims there "took them off the merge path altogether and made a green
floor mean nothing about them". That is the same reason this branch refused the
long-home escape for its own arms rather than taking it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbNFKK8mf6wBeVaCr22iNV
…ual to one

Merging #10886 surfaced two regressions this branch caused, both real and both
invisible to every witness that existed before that PR landed. Measured against
origin/main to establish they are mine and not pre-existing: all three of
v2.test.self_host.stage0_production_target's emission claims are GREEN on
origin/main and RED with this branch's collector change.

FIRST, the join produced the wrong representation of a string. emit_produced_module
folded with list_append over an `Empty` FreeMonoid seed, so its result carried the
CONS-LIST SPELLING of a string rather than the native one. `==` and `concat` behave
identically on it -- which is why every golden over this fold has been green since
it was written, and why this branch's own eight arms, which compare with `==`, could
not see it either. A builtin that requires a native string refuses it:
`split expects a string, got Variant`, from the two stage0 contains-controls, the
first consumers ever to read this fold's output through `split`. Nothing on the
production door reached the fold until this branch routed every nonempty population
through it, so the defect was latent rather than new, and equality-only readers
could never have found it. The join is now `concat` over a "" seed. One
representation for one concept (DESIGN section 3).

The first attempt at this fix changed only the seed and left list_append, and it did
not work -- the measurement said so, and it is the reason the diagnosis above names
the JOIN rather than the seed.

SECOND, the trailing separator moved a golden this branch could not have known
about. stage0_boundary_expected_source pinned "pub fn add(x: i64, y: i64) -> i64
{ x + y }" against the door's output, which now terminates its last declaration
because the door routes through the module authority. The expected text gains the
separator, composed from emit_module_decl_separator rather than transcribed, so a
future change to the module convention reds there instead of drifting silently.
The declaration text stays a literal: it is the fixture golden that claim exists to
pin, and reading it from the profile under test would stop it discriminating.

Verified on the merged tree: all five stage0_production_target claims, this branch's
eight population arms, produced_decl_module_folds_declarations_in_order,
produced_decl_unwired_target_still_refuses, produced_module_two_distinct_fns_assemble
and direct_rust_door_production_group_closing_expectation_holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbNFKK8mf6wBeVaCr22iNV
@briansrls
briansrls merged commit 9002d34 into main Sep 10, 2026
4 checks passed
@briansrls
briansrls deleted the session/sharp-ibex-816 branch September 10, 2026 01:44
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