Skip to content

Unbreak main: the stage0 partition roster gains #9689's three modules, and the required build lane gains the partition crates as the wall - #9711

Merged
briansrls merged 6 commits into
mainfrom
session/neat-stag-793
Aug 30, 2026
Merged

briansrls merged 6 commits into
mainfrom
session/neat-stag-793

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

What was broken

cargo build --release --workspace has been red on main since #9689. v1-stage0-v1-infer fails with 13 errors — E0432 on crate::std_target_representation and crate::extdeps_languages_rust_representation, E0433 on those two plus crate::gunbc_rust_source_type_bindings, all from v1_compiler_coercion.rs, plus one E0282 forced by them. #9689 added those three modules to stage0 lib.rs and to src/v1/stage0/src, but not to the derived crate partition — so the partition crates, which compile each stage0 module through a per-crate #[path] include and a reexport chain rather than through the monolith, had no owner for any of them.

The required gate could not see it. Its build lane ran cargo build --release -p v1-compiler --bins. None of the seven partition packages is v1-compiler, so no required phase compiled any of them, and the gate stayed green over a red workspace for a day.

The roster fix, placed by measurement

Authority is v2.workflow.rust_crate_partition; the generated .dag, the stage0 mirror and the crate lib.rs files all follow by regeneration. Placement is forced by each module's own crate:: references, not chosen:

module additionally depends on unit
std_target_representation std_decl_ref, std_types, v1_rt std-core
extdeps_languages_rust_representation + extdeps_uri, extdeps_external_authority, std_coercion extdeps-languages
gunbc_rust_source_type_bindings + extdeps_languages_rust_representation v1-infer

gunbc_rust_source_type_bindings lands through its own named list rather than being appended to the v1_compiler_* pipeline list: the crate grouping is physical, and the list name should say what the module actually is.

Before touching the compiler I reconstructed the partition closure from the authority alone — the generated rows' modules plus the transitive closure of reexport_packages, joined against the crate:: module references in each rostered module body — and it named exactly those three modules in exactly that one crate, with an empty missing set for the other six. The compiler then agreed. Two independent derivations of the same three names.

The phantom row

std_lens_verdict was in the std-core roster and is not declared in stage0 lib.rs. That is deliberate on lib.rs's side: v2.compiler.self_host.stage0_crate_layout registers it as a SeedRetainedIntrinsicRegistration with has_pub_mod: false. So every partition crate carrying the row compiled a module the monolith does not — one file with two compilation stories.

Disposition of the file: dead source retained under an undeclared reason. Its source module std.lens_verdict no longer exists (the live authority is v2.std.lens_verdict), nothing anywhere references crate::std_lens_verdict, LensVerdict appears in no other stage0 file, and it is absent from the emitted_population.rs manifest. seed_retention_frontier carries retained_reason_undeclared for it.

This PR drops the roster row only. Deleting the file moves the layout authority, the retention frontier, two generated layout artifacts and two crate lib.rs files — and it must also settle src/v1/stage0_core, which is not a workspace member (an orphan crate directory nothing builds, which also path-includes the file). That is a follow-up PR from this lane, not a rider on an unbreak.

The wall

The class is: every crate::N reference in a rostered module must resolve inside its crate's own roster or the transitive closure of its reexport_packages.

The executing wall is the required build lane compiling the partition packages, with rustc as the oracle — gunbc.repo_self_build repo_self_build_partition_crates_command, whose package list is read from the partition roster rather than restated. A crate added to the partition joins the gate with no edit at the gate, so the gate cannot fall behind the authority it guards. That is the property that makes this a wall rather than a one-time repair.

Emitted step, read off the emitted bytes rather than assumed:

      - name: Build the witness fold
        run: |
          cargo build --release -p v1-compiler --bins
          cargo build --release -p v1-stage0-runtime -p v1-stage0-std-core -p v1-stage0-std-surface -p v1-stage0-extdeps-languages -p v1-stage0-v1-artifact -p v1-stage0-v1-infer -p v1-stage0-emit-core

Falsifiers, which are the enrolled evidence: drop a module from a roster → E0432 naming the module and the referrer; a roster entry naming no file → E0583 naming the entry; restore → green.

Not a whole-workspace widening. A declared bin or crate outside both the v1-compiler package and the partition packages is still built by no required phase — the standing 2026-08-25 declared row, which this does not close. Not a restored phase either: gunbc.required_ci_phase_roster (#9698) makes the four required phases a closed coproduct with PartitionCrates deliberately absent under the CI-bankruptcy rung drop. This adds a build step to an existing lane; it does not retire that drop, whose restoration trigger — a change-derived required population — is untouched.

Why there is no hermetic witness for that class

Both routes are closed. A witness reading src/v1/stage0/src to recover crate:: references declares ReadsLiveTree, and the floor's admission test declines those (DeclinedOutsideRequiredGate's sibling arm), so it would execute nowhere while being cited as coverage. Joining against a committed carrier instead needs the Rust module dependency graph, which gunbc.stage0_rust_product_reachability declares unmodeled in its own words — rust_module_dependency_graph_unresolved_edge, "Rust module dependency graph not modeled — cannot walk from cargo bin roots to dependent source files" — with every non-root path refusing on exactly that edge.

Next-rung trigger, recorded and not built here: the Rust module dependency graph modeled as a committed carrier. The crate:: closure then becomes decidable without reading the tree, and the join can replace the build as the wall.

A totality join was drafted and rejected as a change detector: union of the six crate module lists == stage0_all_partitioned_modules is measure() == measure(), because stage0_all_partitioned_modules is the concat of those same six lists in the same file. Deleting a module from stage0_extdeps_language_modules changes both sides and stays green.

The witness, and exactly what it claims

v2.test.claim.stage0_partition_roster_layout_join_witness catches one class: a roster entry naming a stage0 file the crate-layout authority registers with has_pub_mod: false — the std_lens_verdict class. It is not evidence about the E0432 class, and its header says so.

It was RED on the required path before the regen — required-floor: FAIL v2.test.claim.stage0_partition_roster_layout_join_witness.w_live_partition_roster_declares_no_undeclared_module returned Bool(false) (run 33293054935) — and green after. An earlier revision sat at test.claim.*, which matches none of required_gate_prefixes, so the floor discovered it, classified it DeclinedOutsideRequiredGate and never ran it while it was red. The module path is part of the wall.

Its discriminating RED is a fixture, because once the phantom row is gone the live rosters and the seed-retained filename roster are disjoint, making the live assertion an empty-set claim whose condition is absent — green whether the detector works or not. §4b's rule for that shape is that a state unrepresentable in the accepted corpus may still be representable in a fixture, and the detector is a pure fold over rows. The fixture cites the real std_lens_verdict registration rather than an invented name, so it also reds if the layout authority ever grows a pub mod for that file.

witness_v1_infer_unit_members_holds was (unit.members |> count) == 9 and went stale the moment the roster gained a module. It is not retyped to 10: it is now an identity join between the declared roster and what came back through the assignment map and partition_fold, so it cannot be repaired by editing a number. Its blind spot — an error in the roster itself, where both sides move together — is stated in place. Sibling unit witnesses still carry count literals; converting them is a separate diff.

Cost

One remote dispatch, 15 cores, same target dir, in order:

measurement seconds rc
BINS_COLD — what the build lane compiles today 308 0
PARTITION_ADDED — the seven partition packages on top 47 0
BINS_WARM — control 236 0
PARTITION_WARM — control 0 0

+47 s on the build lane, and zero on the required check. The two required lanes run in parallel under an aggregate context, so the check's wall clock is max(build, floor), not the sum. Last run: build 11m12s, floor 15m09s; 11m12s + 47s = 11m59s, still under the floor lane. Free until the build lane grows past the floor by roughly three minutes.

⚠️ BINS_WARM is 236 s, not ~0 — a trap for the next author. Building the partition packages invalidates part of the v1-compiler bins build: the two invocations select different package sets, so cargo unifies shared dependency features differently and rebuilds them. PARTITION_WARM at 0 s shows the invalidation is one-directional. The emitted step is unaffected (bins first, partition second, paying the measured 47 s), and the floor lane is a separate job with its own target dir — but a step added after mine in that job would silently pay ~236 s, which the +47 s figure alone does not show.

rc=0 on PARTITION_ADDED is also the wall's positive control: the same command that produced the 13 errors now compiles green.

Regeneration receipt

Produced by the generators, not by hand, and tightly scoped:

  • main_wet rewrote exactly two artifacts — the generated partition .dag and witnesses.yml. Nothing else in the corpus moved.
  • --required-regen reported exactly one drifted mirror: gunbc_stage0_crate_partition_generated.rs.
  • --emit-partition-crates rendered 14 and wrote 3: the std-core, extdeps-languages and v1-infer lib.rs — precisely the three crates the roster change touches.

Regen note for the next author: main_wet is OOM-killed on a BuildBuddy runner under the default budget (SIGKILL, rc=137), and it is silent through a cmd | grep | tail pipeline because the pipeline reports the last stage's status. /sys/fs/cgroup/memory.max and memory.high are unreadable on those runners, so memory_governor::read_host_budget_bytes falls through to MemAvailable — the host's memory, not the slot's — and caps at the 15 GiB declared slot line, which can still exceed the slot. Set GUNBC_MEMORY_BUDGET_BYTES explicitly (10 GiB here; peak RSS 7.7 GiB). Filed separately as a governor fail-open; not fixed here.

gunbc-ci-auto-heal and others added 5 commits August 30, 2026 03:28
…ow, and the required gate that can see them

The authority edits only; generated artifacts follow in the next commit.

- v2.workflow.rust_crate_partition: std_target_representation into the std-core
  unit, extdeps_languages_rust_representation into the extdeps-languages unit,
  and gunbc_rust_source_type_bindings into the v1-infer unit through its own
  named list, because its layer is neither the v1_compiler pipeline nor the
  extdeps one and the grouping is physical rather than nominal. Placement is
  forced by each module's own crate:: references, not chosen.
- The same authority drops std_lens_verdict, which named a file stage0 lib.rs
  deliberately does not declare (has_pub_mod false in the crate-layout
  registration), so the partition crates were compiling a module the monolith
  does not. The FILE's disposition is deliberately not settled here.
- gunbc.repo_self_build gains repo_self_build_partition_crates_command, whose
  package list is read from the partition roster rather than restated, and the
  build lane runs it beside the all-bins build. That is the executing wall for
  the E0432 class, with rustc as the oracle.
- One witness for the class a hermetic join CAN reach, with its discriminating
  RED authored as a fixture because the live corpus cannot express the refused
  state once the phantom row is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTvKD3J3pnUzXmUascawfQ
…e v1-infer count literal to an identity join

Run 33290382542 measured both of these rather than my guessing at them.

- The new witness sat at `test.claim.stage0_partition_roster_layout_join_witness`,
  which matches none of v2.workflow.required_floor required_gate_prefixes, so the
  floor discovered it, classified it DeclinedOutsideRequiredGate and never ran it
  -- while it was RED on the live corpus. A wall that reds and is declined is an
  inert lens, so the module path is part of the wall and the file now lives at
  v2.test.claim.*, which the `v2.test.` prefix admits.
- witness_v1_infer_unit_members_holds was the run's single unexpected failure:
  `(unit.members |> count) == 9`, stale the moment the roster gained a module. It
  is now an identity join between the declared roster and what came back through
  the assignment map and partition_fold, so it cannot be repaired by retyping a
  number. Its blind spot -- an error in the roster itself -- is stated in place
  and belongs to the build lane, where rustc is the oracle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTvKD3J3pnUzXmUascawfQ
…merging -- this refuses EVERY pull request, not just this one

Not this lane's subject, taken because it stands between every branch and a
green. #9698 (fa03a01) moved BuildLane and WitnessesLane from
gunbc.required_ci_host_verdict_census to the new gunbc.required_ci_phase_roster.
The two TransitionAdmission rows that adjudicated that move were authored with
the trigger "they go STALE the moment #9698 merges and MUST be removed then" --
their own words -- and #9698 is merged, so no pull_request build can produce the
deltas they name and the phase refuses on the stale rows alone.

Measured, run 33292088803: FAILED PHASE namespace-wave-admission (0 unadjudicated
delta(s), 2 stale admission(s)), naming both rows. This branch's own namespace
delta -- the gunbc.repo_self_build -> gunbc.stage0_crate_partition_generated
import the roster fix adds -- was adjudicated ExplicitlyEvaluatedZeroDelta and
passed, so the refusal is entirely the two stale rows.

Emptying the array to &[] is the proven form: the fourth shrink (#9705) did
exactly that and shipped green. That shrink was a standalone PR and that remains
the normal shape; it rides here only because main was already red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTvKD3J3pnUzXmUascawfQ
# Conflicts:
#	src/v1/stage0/src/namespace_wave_admission.rs
@gunbai-bot gunbai-bot Bot changed the title Main whole-workspace build red since #9689: stage0 partition roster lacks 3 new modules and the required gate cannot see it (builds 2 bins) -- fix roster, add the identity-join wall Unbreak main: the stage0 partition roster gains #9689's three modules, and the required build lane gains the partition crates as the wall Aug 30, 2026
… lib.rs files, and the build lane that now compiles them

Produced by the generators, not by hand. main_wet (dag/gunbc/instruments/
generated_artifact_gate.dag) rewrote exactly two artifacts -- the generated
partition .dag and .github/workflows/witnesses.yml -- and nothing else in the
corpus moved. --required-regen then reported one drifted mirror,
gunbc_stage0_crate_partition_generated.rs, and --emit-partition-crates rendered
14 files and wrote 3: the std-core, extdeps-languages and v1-infer lib.rs, which
are precisely the three crates the roster change touches.

The emitted build step, read off the emitted bytes rather than assumed:

    run: |
      cargo build --release -p v1-compiler --bins
      cargo build --release -p v1-stage0-runtime -p v1-stage0-std-core ...

Both commands under one literal block scalar, the package list derived from the
partition roster rather than restated, so a crate added to the partition joins
the gate with no edit at the gate.

REGEN NOTE FOR THE NEXT AUTHOR: main_wet is OOM-killed on a BuildBuddy runner
under the default budget (SIGKILL, rc=137). /sys/fs/cgroup/memory.max and
memory.high are unreadable there, so memory_governor read_host_budget_bytes
falls through to MemAvailable -- which reports the HOST's memory, not the slot's
-- and caps at the 15 GiB declared slot line, which can exceed what the slot
actually has. Setting GUNBC_MEMORY_BUDGET_BYTES explicitly (10 GiB used here,
peak RSS 7.7 GiB) makes it complete. The kill is silent through a `cmd | grep |
tail` pipeline, because the pipeline reports the last stage's status.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTvKD3J3pnUzXmUascawfQ
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 30, 2026 06:21
@briansrls
briansrls merged commit 8371bd7 into main Aug 30, 2026
4 checks passed
@briansrls
briansrls deleted the session/neat-stag-793 branch August 30, 2026 06:24
briansrls pushed a commit that referenced this pull request Aug 30, 2026
…tion denominator made physical (#9731)

* The observer could not see the host it existed to retire: the observation denominator made physical

srv6 ran a live, enabled serving unit and the converge plan minted zero typed
actions and called the fleet clean. Measured, not reasoned: fleet-converge run
33296910990 reported observed_hosts=1, and run 33297525985 wrote
spark_serving_typed_actions.wire at 0 bytes.

spark_serving_observe_ci_wet enumerated spark_serving_desired_hosts. That was
correct while desired named every Spark, and became wrong the moment #9679 gave
desired a role scope. Retirement is planned from members observed and NOT
desired, so scoping observation by desired makes the retirement population
unobservable by construction -- observed minus desired is empty for every host,
always. The module already refused an EMPTY observation list on the ground that
emptiness is ignorance rather than a clean fleet; the roster held {srv5}, which
is not empty, so nothing fired.

That is empty_observation_narrow in its non-empty form.

The obvious repair -- union the two role rosters -- is rejected here and the
rejection is why this is a module rather than a one-line edit. Both role
projections answer through SparkCellRoleUnique and deliberately drop
SparkCellRoleAmbiguous and SparkCellRoleAbsent, so their concatenation silently
omits a host whose role assignment is broken. An absent or contradictory ROLE
says nothing about whether a serving PROCESS runs on the metal, and a broken
assignment is precisely when stale serving state must stay visible. That repair
would have fixed today's srv6 and preserved the class one input away.

So the scope reads the procurement authority, which derives the enrolled Spark
identities from the router bindings and knows nothing about roles. Roles decide
what may be DONE to a host; they do not decide whether it is LOOKED AT.

Coverage becomes an identity join and SUBSUMES the cardinality guard rather than
standing beside it -- two authorities for one question. Observed keys must equal
expected keys, each exactly once; missing, foreign and duplicated are carried
separately because they are different defects with different causes.

Evidence: 8 claims under v2.test.*, which the required floor executes -- the
same conclusion #9711 reached when it moved a RED witness under the required
prefix rather than leaving an inert wall. They are pure over host-identity lists
and reach no fleet planner. Discriminating control run: reinjecting the defect
reds 5 of 8; the 3 that stay green are the pure coverage-classifier claims,
which consult no scope producer and correctly test a different subject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dHefbXnLiW1XNs2vVxhwj

* The comment claimed a replacement the diff had not performed

Review 57545 (non-blocking): SparkObservationCoverage is defined and exercised
by claims but not consumed by serving_observe, which still refuses on the count
the join was written to subsume -- while the module comment described the
subsumption as done.

That is the overstatement this module exists to correct, one layer up: prose
asserting a migration that execution has not performed. The PR body named the
gap; the source did not, and the source is what survives.

The comment now states that the replacement has NOT happened, that the two
authorities genuinely coexist until the observer's report is built from the
join, that the narrowed-roster case is currently caught by the claims rather
than by the production path, and that the cutover is the remaining P0-C
obligation. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dHefbXnLiW1XNs2vVxhwj

* Both observer entries answer 'did the probe answer', and the causal chain is unwelded

Two corrections from tracing the live dataflow rather than reasoning about it.

TERMINAL POLICY (a defect this branch introduced). Widening the roster to the
physical Spark population put a RETIRED cell inside it, and a retired cell is
structurally SparkServingAbsent. The workstation entry terminated on
spark_serving_observation_report_succeeded, under which absent counts as
failure, so the exactly-correct final state -- srv5 serving, srv6 retired --
would have exited non-zero. Fail-closed, but the wrong answer for an instrument
whose job is to describe what is there. Both entries now terminate on
spark_serving_report_observations_were_made.

The row that is no longer the terminal is retained and documented rather than
deleted: its only consumers are claims in test.claim.spark.*, which is outside
the required gate closure, so editing that module would classify its witnesses
as changed-and-declined and red the required floor. It is blocked from deletion
by the same enrollment gap that owns the 59 nonterminal identities, and that is
recorded at the function rather than hidden.

CAUSAL CHAIN. The module comment cited fleet-converge run 33297525985's 0-byte
spark_serving_typed_actions.wire as evidence for the observer defect. False in
the live dataflow: fleet_converge_plan_cli passes observed_spark: [] and
fleet_converge_plan_artifact sets spark_serving_typed_actions to
scheduled_rows_empty() unconditionally, so the live planner reads NO Spark
observer and would have emitted an empty wire whatever this module answered.
Independent defects, repaired separately. The comment now says so, including why
the chain was tempting.

Evidence for the terminal swap is authored (spark_serving_observe_witness_test
asserts was_made && !succeeded) and does not execute. Restating it under
v2.test.* was attempted and abandoned on measurement: reaching the predicates
pulls this module's 46-import closure and kills the claim module during resolve.

8 scope claims re-verified 8/8 on the refreshed head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dHefbXnLiW1XNs2vVxhwj

---------

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>
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