Skip to content

The wave-admission wall: adjudicate closure, subject-membership and binding deltas before a namespace change merges - #9365

Merged
briansrls merged 31 commits into
mainfrom
session/nimble-moth-416
Aug 27, 2026
Merged

briansrls merged 31 commits into
mainfrom
session/nimble-moth-416

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The wave-admission wall: the enforcement mechanism the namespace plan names as a BLOCKER

gunbc.plans.import_namespace_program §9 records, in its own words, that "no CI mechanism enforces any of this — no ratchet, no phase, no gate over the import population", and the 2026-08-26 operator ruling carried at gunbc.compiler_frontend_program_interlock converts that disclosure into a BLOCKER: milestone_prerequisites gates NamespaceFirstSemanticWave on NamespaceWaveAdmissionEnrolled by name. This PR is that milestone.

The plan's own sentence is the acceptance condition, and it is the one I built against: a plan that reads as governed when it is not is worse than one that reads as unguarded. So the deliverable is not assurance added to a governed program — it is closing the gap between what the plan CLAIMS and what is true.

What runs

A new required phase, namespace-wave-admission, in claim_executor --required-ci, witnesses lane, ordered after parse.

  • It acquires no second corpus. run_dag_parse_sweep already parses every authored .dag on every required run; that index is the wall's HEAD side. The BASE index is the head index with the diff applied in reverse at file grain — only the files the change touched are parsed again, from their base blobs — while closure and bindings are recomputed over both whole graphs, because a module whose own text did not move can still have its subject moved by one that did.
  • The change class is DERIVED, never declared. NamespaceChangeClass splits preparatory work from work that alters membership or binding; nothing asks an author which they wrote. PreparatoryNoSemanticMotion is a measured property of a diff, not a claim in a PR body.
  • It admits on UNADJUDICATED delta being empty, never on delta being empty — the one-word difference the ruling states explicitly, and the weaker spelling of which would refuse the cut itself and then be repaired by weakening the wall.
  • Two non-verdicts are reported under their own names and only one passes. NoSubject (a push whose baseline IS its head — main after a squash merge) compared nothing and admits nothing. NotEvaluated (a baseline that does not resolve) refuses, per the ruling's partition: I could not see what changed and nothing changed are different states.
  • A stale admission refuses too — a row matching no delta is a permission standing over nothing.

The three deltas, and the one that is not a wall

Closure is a pure function of membership, so the closure moved and no membership moved is not a state any fixture can author. An arm for it would be permanently green by construction — the decoration §4b calls worse than absent. So closure is measured and attributed, never separately adjudicated: each delta carries the number of modules whose subject it moves, so a refusal names its blast radius, and the adjudication happens at the generators. Saying so is the difference between a scope statement and an inflated one.

Two design decisions that took the whole shape

1. The grain is authored containment identity, and that is a CLOSED result. v2.workflow.legacy_binding_delta establishes it for its own subject and the argument transfers unchanged: std.occurrence_identity's scope law forbids filename, span, authored name, structural equality and content hash as identity inputs, and an OccurrenceId is a monotone counter consumed in walk order, so it encodes POSITION. A cross-compile correspondence is something a transformation emits — and between a merge base and a PR head there is no transformation to emit one. An occurrence-grain delta between two arbitrary trees is therefore unavailable by construction, not a better key waiting to be found. The wall uses the identity legacy_binding_observation legacy_subject_identity already folds: module path, enclosing declaration, and the leaf segment of the reference. A row's value is the SET of declaring identities, never a winner — so a local declaration arriving beside an imported one widens the set to two and reports NewAmbiguity rather than silently picking the nearer, which is the mechanism the namespace authority exists to delete.

The leaf rather than the whole spelling is a fixture finding, not a preference: keyed on spellings, widget and probe.home.widget are two rows, so requalifying a reference reads as one name losing its declaration — and requalification is the namespace program's own core motion. A wall keyed on spellings would refuse the program in its entirety.

2. The binding channel is not the import channel. A wall reading bindings only through import members would observe the import-name universe being deleted and then see nothing at all — blind on precisely the change it gates. So it reads every authored name occurrence in a module's own parsed tree and resolves it independently (dotted spelling by longest module prefix; bare spelling against the module's own declarations and its import claims, following re-exports to the module that actually DECLARES the name). It survives the cut because it never depended on the construct being cut, and the function is identical on both sides, so a base measured before a cut and a head measured after it are measured by one instrument. It is parse-then-derive over the Node tree, not the raw-text scanner DESIGN ruled a heuristic.

The RED is the deliverable — and it caught a wall that would have refused the program it exists to admit

The headline is not the seven mutations, it is the two arms that failed on their FIRST run. One of them was this: keyed on the whole spelling rather than the leaf segment, requalifying a reference reads as NewUnresolvedness — and qualification is precisely what the namespace program's projection does. The wall would have refused the only program it exists to admit. Nothing about the code was inconsistent, so no amount of reading would have found it; a fixture going red did.

adjudicate takes two indexes and no git, so the fixture boundary — which is where §4b says reachability is judged — authors a base tree and a head tree and reads back the exact disposition. Every refusing arm is one mutation of the auto-admitted control.

Measured by running each mutation ALONE against the production predicate, with a restore between and a re-run confirming the restore returns to green with no residue (cargo test -p v1-compiler --test namespace_wave_admission, remote runner):

mutation of the production code arms that went red
NewUnresolvedness arm → TargetChanged a_spelling_that_stops_denoting_anything_refuses_as_new_unresolvedness
NewPoolCoincidenceResolution arm → TargetChanged a_spelling_that_begins_denoting_something_refuses_as_pool_coincidence
NewAmbiguity arm → TargetChanged a_second_declarer_on_the_chain_refuses_as_new_ambiguity, a_local_declaration_beside_an_import_is_a_two_member_set_not_a_winner
membership_supported always true removing_membership_nothing_bound_through_is_admitted_as_unused
admission matched on disposition alone, ignoring subject an_admission_naming_a_different_subject_does_not_admit_and_reports_stale
binding rows keyed on the whole spelling instead of the leaf dropping_an_import_for_a_qualified_spelling_keeps_the_declarer_and_is_admitted
auto_admitted returns true for every disposition four arms, including both admission arms

Baseline green before the first mutation; green again after every restore; 14 arms, 0 failures. Two mutations red more than one arm and both are the same branch or the same partition covered from two directions — no arm is red under a mutation that does not reach it.

Two arms failed on their first run and both were real findings in the production code, not fixture defects: the leaf-keying correction above, and a fixture whose "unused" import supplied a leaf the module reached by another route — a malformed plant reading as a wrong verdict, now guarded by a plant assertion.

The vocabulary join

The host NamespaceDeltaDisposition is a second representation of the .dag coproduct. The exhaustive match protects this file's internal consistency and says nothing about the carrier: a variant added to the ruling and not here compiles perfectly. So the phase runs a set-equality join in both directions against the authority's own parsed variants, before any verdict — and an absent authority refuses, because that is the state in which nothing is checking the vocabulary.

What this does NOT claim

  • The admission roster is EMPTY, and that is the correct landing state — no wave has run, so no transition has been authorised. The coarse class-admission carrier is deliberately NOT built: it would be a mechanism with no consumer until a wave needs it (§6, experimental residue). What is fixed today is the CONSTRAINT on its shape, recorded in the carrier: a class admission must be bounded by enumerated identity, never by predicate. "These exact bindings, taken from the pre-deletion baseline observation, become unresolved" is admissible; "unresolvedness is expected during the wave" is the absorbing fallback wearing the wave's clothes.
  • No population figure here is live. The order-of-magnitude expectation for the first wave (thousands of transitions, which is what decides the admission-grain question) comes from snappy-dove-250 and is marked in the carrier as STALE AS A POPULATION with the reason.
  • The occurrence-grain ceiling is declared as a GuaranteeStall, with its blocker stated as the closed result above rather than as effort.
  • Nothing outside .dag under the three sweep roots is observed.
  • The hand-Rust growth is NOT claimed as approved. 25 declarations are enumerated as a SeedGrowthJustification with reason, trigger and current boundary, and classified against the v1 freeze on the same purpose test declaration_index landed under — but that is my classification, offered as evidence for a reviewer, not a permission I hold. An author who can write a scaffold can equally write a row claiming it was approved (DESIGN, 2026-08-10). warm-hawk-909 is taking it to the operator alongside Replace hostile-writer shell scaffold with typed host effect #9363's, so both are decided against one test. A reviewer telling me the classification is sound is not the same as admitting the growth.

Cost

Measured on the live corpus (4,099 modules) on the remote runner:

  • parse sweep (already paid by the parse phase): 21.1s

  • identity arm — the same corpus on both sides: 4.2s, 0 deltas, over modules_compared=4099, membership_edges_head=21940, binding_rows_compared=724873. The denominator is the other half of that zero: a green that cannot say what it covered is an instrument failure wearing coverage's clothes.

  • a live RED, on a real module: taking dag/gunbc/accelerator_demo_plan.dag with its one real import removed as the head, the wall reports exactly one delta over the same 724,873 rows —

    NewUnresolvedness binding gunbc.accelerator_demo_plan::demo_accelerator_placement `LocalAccelerator`
      — base {std.realization} -> head {} [closure blast radius: 0 module(s)]
    

    and refuses (unadjudicated=1). Note what it did not report: the std.realization membership edge SURVIVES, because that module reaches it by dotted spelling at five other sites, so the edge is genuinely still there. The wall said only the true thing.

Corpus integrity after the change, from the standalone sweep at this head: 4099 file(s) parse-clean; declarations modules=4099 declared=78825 import_members=82277 citations=1552 debt=42 in_fixtures=173 outside_index=174 kernel_named=2041 lens_modules=70 — zero findings, so the new carrier's imports and every one of its citations resolve. Rust suite (not in CI, run remotely): namespace_wave_admission 14/14, declaration_index_integrity 25/25.

So the phase costs about 4 seconds on top of a parse the required run already performs, and it acquires no second corpus.

Its first catch was its own dispatcher

While measuring the wall over the real corpus I found that the branch this one stacks on — #9352, session/warm-hawk-909 at a5a4110b7b — failed the required parse phase on eight // annotations authored inside data … = [ … ] initializers, which §4c does not admit. Reported, and fixed by that session at 233c358ee3 before this PR merged their updated head.

It is worth recording for a reason beyond the fix: the previous .dag walk would have returned CLEAN on all eight, because tokenize routes // into the annotation channel and grain is decided later — DESIGN carries that receipt. A check that existed, ran, and could not see the thing it appeared to cover is exactly the failure mode this wall is built not to be.

It has executed on the real path

Not a local measurement — the phase running inside witnesses.yml, floor lane. Taken from run
33023922255, all three jobs green (build, floor, witnesses), on this PR's current
head 930ef47a0.

State the provenance exactly, because the wall's own subject depends on it: the phase reports
head=a946db44, which is the PR merge ref GitHub constructs, not the branch tip. That is
the correct subject — the question the gate asks is what this change does when merged — and
saying so is the difference between a receipt and a number.

required-ci: phase namespace-wave-admission ROUTED to lane witnesses (not this job)      ← build lane
required-ci: phase namespace-wave-admission (closure, subject membership, binding)       ← floor lane
required-ci: namespace-wave-admission base=6295acdc head=a946db44
  modules_compared=4103 modules_added=4 modules_removed=0 membership_edges_head=21987
  binding_rows_compared=726813 closure_rows_moved=741 deltas=3
required-ci: namespace-wave-admission ExplicitlyEvaluatedZeroDelta membership
  gunbc.plan_registry_batch_g -> gunbc.plans.import_namespace_program
  — added; reached by a name this module authors [closure blast radius: 356 module(s)]
required-ci: namespace-wave-admission ExplicitlyEvaluatedZeroDelta membership
  gunbc.plan_registry_batch_g -> gunbc.plans.v2_corpus_self_host
  — added; reached by a name this module authors [closure blast radius: 356 module(s)]
required-ci: namespace-wave-admission ExplicitlyEvaluatedZeroDelta membership
  gunbc.seed_growth_admission -> gunbc.namespace_wave_admission
  — added; reached by a name this module authors [closure blast radius: 29 module(s)]
required-ci: namespace-wave-admission ADMITTED — every delta is auto-admitted or named by a transition admission

Read what that says, because it is the whole design executing on its own diff:

  • The merge base resolves in CI, so the NotEvaluated arm is not the one this repository lands in — the risk I could not close from a remote runner (which fetches at depth 1 and has no origin/main) is closed here.
  • The three deltas are exactly this PR's three real membership additions — the two plan registrations and this PR's own seed-growth import — over 726,813 binding rows and 4,103 compared modules. Nothing else moved, and the wall said nothing else moved.
  • Each is ExplicitlyEvaluatedZeroDelta, derived from the fact that a name the module authors reaches into the target. Nobody classified them; the wall did.
  • closure_rows_moved=741, with blast radii of 356, 356 and 29. This is the part that would have been a third wall: the closure motion is real and large, it is attributed to the three membership edges that generate it, and it is adjudicated at those generators rather than 741 more times.
  • The phase costs about five seconds of the floor lane (23:39:15 → 23:39:20), on top of a parse the required run already performs.

An earlier revision of this section reported run 33020161158 at head de1445990ce8, and said so
under a caveat that the RUN had been cancelled while the PHASE completed first. That caveat is no
longer needed and the numbers are replaced rather than annotated: two receipts for one claim is two
accounts of one fact.

Stacking

This branch merges session/warm-hawk-909 (#9352), because the wall consumes gunbc.compiler_frontend_program_interlock's disposition vocabulary and citing it from main would resolve to nothing. Those four files are #9352's, not this PR's. When #9352 merges I will merge main and the diff narrows to this change alone.

Independence

Dispatched outside the program it gates — snappy-dove-250 declined ownership on the ground that a program parented over its enforcement grades its own homework. I took the domain semantics from them (all three answers changed the design) and kept what counts as ADMISSIBLE here.

🤖 Generated with Claude Code

Hand Rust: the growth, and what it bought back

Operator, relayed: "yes we need to stop adding hand rust asap" and "use it as an opportunity to migrate/delete hand rust". Answered at declaration grain, in gunbc.namespace_wave_admission namespace_wave_admission_seed_growth_justification, so the argument is a typed carrier rather than a PR-body claim.

(2) What this PR deletes or migrates — measured, not argued.

  • leaf_of DELETED. It was a nickname for v1.00_core qualified_last_segment, the symbol v1.05_emit_rust rust_fn_sig_leaf_name_dotted_note names as THE authority for taking an authored spelling to its last segment. The wall now calls that mirror. This is the stronger construction as well as the smaller one: the reduction the wall keys every row on is now the corpus authority instead of a local respelling — a §3 fork this change had introduced and removed before landing.
  • render_set DELETED, inlined at its two call sites.
  • git_stdout: a NET REDUCTION of one pre-existing hand declaration. claim_executor carried a byte-identical private copy. The wall needed the same helper; rather than land a third spelling, the lib owns the one copy and the bin's is deleted and imported.
  • Four inherent methods became free functions, because std.decl_ref offers only WholeDeclaration and NamedField. A method on an impl block is uncitable — as methods they were items the roster structurally could not enumerate. The module now carries no impl block.

(1) Can any of the residue be modeled instead? Per class, with the blocker named.

  • Class A — the pure fold, 28 declarations (adjudicate, binding_rows, binding_disposition, declaring_candidates, declarer_of, closure_of, membership_map, direct_membership, module_prefix_of, membership_supported, blast_radius, disposition_label, disposition_auto_admitted, delta_subject_render, report_unadjudicated, render_delta, vocabulary_findings, and the types and constants they fold over). Every one is a pure function of two indexes and needs no host effect. They are Rust for exactly one reason, and it is not effort: their input type is host-only. ModuleDeclarationRecord and DeclarationIndex are produced by a host walk and have no .dag carrier. Authoring one here would be a second representation of a type gunbc.declaration_index_seed_growth already owns — the §3 violation this wall exists to refuse elsewhere — so the correct move is to follow that carrier's migration rather than fork it. Its own trigger already names this class ("the record builder and every finding function follow it"); this roster is one of those consumers.
  • Class B — the acquisition, 4 declarations (git_stdout, in_sweep_scope, base_records, run_required_wave_admission). Named separately because they are blocked on a different grounding: there is no typed repository-read transport a .dag fold can call to obtain a file's text at a ref. That is the scm lane's subject, not a surface this change could have authored.

(3) The count went UP, 25 → 33, and that is the honest direction. The roster was joined against the module's actual declaration population instead of listed from memory; six declarations were missing from the first draft. gunbc.seed_growth already warns that hand_authored_declarations is an authored obligation roster rather than a derived denominator — this is that warning firing on its own first use, caught here rather than by a reviewer.

What was NOT done: the wall's coverage was not shrunk to lower the number. A smaller wall that adds less Rust spends correctness to buy a metric, which is the ratchet failure DESIGN names from the other direction.

A trap worth naming, met while resolving the merge

/usr/local/bin/gunbc is a June 26 binary. Running the generated-artifact gate through it
fails with a wall of error: function 'X' not found in scope — including fold, the
substrate's core catamorphism. That reads as corpus breakage and is not: the symbols are absent
from the old binary's view, not from the tree. If fold were genuinely missing, nothing would
compile.

Build the current one and invoke it by path:

ctrl-build --local -- cargo build --release -p v1-compiler --bin gunbc
./target/release/gunbc run --source-root dag --source-root src/v2 \
  --entry dag/tools/generated_artifact_gate.dag --function main_wet

It must be local: BuildBuddy runners are linux/amd64 and the session container is arm64, so a
remote build yields a binary that cannot execute here — and the regenerated files have to land in
this worktree anyway.

Recorded here rather than left in two people's heads, because anyone hitting it cold will file a
bug against the tree.

Brian Searls and others added 21 commits August 26, 2026 19:30
… ratchet by symbol

Opened by operator ruling 2026-08-26: v2 self-hosts the ENTIRE v2 corpus, started
from scratch, superseding the 2026-08-16 root-partition document whose evidence
base was bankrupted (all ten probe links dangling, census a month stale, its
instrument deleted).

The plan is a .dag Plan carrier rather than a hand-authored .md so that its claims
are joined to symbols a machine checks: v2_corpus_self_host_ratchet_bindings names
the ratchet, the measurement instrument, the hosting admission and the emission
phase as DeclarationRefs, which the ingestion-time citation wall resolves on every
required run. A section that goes stale because its mechanism was renamed refuses
here instead of reading as current -- the failure mode that killed the last anchor.

Records two findings the program depends on: the required v2-emission phase never
invokes cargo ("stopping before cargo"), so no required check measures rustc; and
the whole-corpus compile IS hostable on a CI runner, correcting a report that no
host could run it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The plan landed with both a .dag Plan carrier and a hand-authored docs/plans .md.
That is a second representation of one fact with no authority over it -- the §2/§3
parallel-representation debt -- and it would drift from the carrier silently, since
nothing joins them.

Evidence the .md is not required: gunbc.plans.branch_merge_admission_model is a
registered plan with no docs/plans markdown at all. The Plan type already carries
plan_to_document, so the markdown is DERIVED where it is wanted rather than
authored beside the source it restates.

Operator steer 2026-08-26: design doc changes are .dag changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second anchor requested by the operator alongside the v2-corpus-self-host
plan. Material supplied by the owning session (snappy-dove-250) on request.

Bound to two symbols only -- gunbc.namespace_cut_landing_order
current_landing_order and namespace_cut_grammar_last_ruling -- both
verified to resolve before authoring. Everything else is marked as prose
in the text rather than given a citation it cannot support. A long
half-bound roster would assert that the ingestion-time citation wall is
checking claims it is not checking.

Three things the plan deliberately does not smooth over:

- The strip measurement is STALE AS A POPULATION and current only as a
  CLASS TAXONOMY. The corpus sha256 is the anchor, not the commit line.
- smart-wolf-868's placement at Step 2 is ASSUMED, not measured, and is
  labelled so where it appears.
- Whether the namespace cut blocks v2 self-compile or the reverse is an
  OPEN QUESTION carried to the operator, not a position taken here. It
  changes wave ordering in both plans.

Ordering claims bind to the carrier, never to a document sentence: the
execution document is superseded on ORDER only (operator, 2026-08-25),
which is why grammar deletion lands LAST rather than first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four fixes, all from review rather than from me.

1. S1 asserted #9346 was "still OPEN with conflicts ... contrary to a
   report that both had merged". #9346 is MERGED (2026-08-26T19:06:10Z,
   7fcdbdc, verified an ancestor of main). The line did not merely
   carry a stale fact -- it instructed the reader to distrust an accurate
   source, in an ACTIVE anchor. Fixed by DELETING the assertion rather
   than updating it, and recording the rule: a plan carrier must not
   assert the open/closed state of a PR. It rots in hours, it is free to
   re-derive at read time, and nothing refuses when it goes stale. This
   is the one class the evidence-bankruptcy rule could not have caught,
   since it is not a measurement at all.

2. The seed partition table is a TRANSCRIPTION with no entry point.
   An independent re-run of the described procedure on the same commit
   reproduced every figure exactly except emitted lines: 166,834 vs
   166,727, the total carrying the same delta. No conclusion turns on
   107 lines; the finding is that a described procedure and an
   instrument are different things, which is what name-the-instrument
   predicts. Named as the gap it is.

3. The superseded census's CONCENTRATION is restated as a hypothesis
   with a test attached. Its two halves do not decay alike: the
   magnitude is inert without a board, but the concentration is a claim
   about the emitter's failure distribution and the emitter has been
   changed for a month by lanes whose purpose is moving it. A magnitude
   drifts; a concentration can invert, and sequencing by a stale one
   puts effort where the wins are already taken.

4. Import/namespace section 5 no longer claims the eleven classes "are
   still the right partition". Nothing has tested that. The taxonomy is
   what survives; its COMPLETENESS is unverified, since a class of
   breakage introduced since the measurement would not appear in it.
   Staleness is now stated as MEASURED -- the anchor recipe re-run gives
   a different corpus hash on a tree behind main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rule against asserting PR state in a carrier was filed against #9346,
which had merely gone stale. A stronger receipt arrived the same night on
#9349: three readings within minutes -- dashboard reporting failing from a
superseded run, a peer re-deriving green at check-run level, and a third
check finding the head had moved again and the PR was mid-run. Each was
correct when taken; none described the PR when quoted.

That is the case #9346 could not make. There a correct measurement never
existed; here there was a correct measurement at BOTH ends and the shared
conclusion was still wrong. The mechanism is that a PR-state sentence has
no spelling for AS OF WHICH HEAD, so a true reading and a stale one become
indistinguishable the moment either is passed on -- which is precisely what
CORRECTING the line would have reproduced, and why it was deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling 2026-08-26 answered the sequencing question both plans had
open, and answered it at a different grain than it was asked: neither
program blocks the other whole. Self-host's proof envelope blocks the FIRST
namespace semantic wave; namespace completion blocks self-host's
IRREVERSIBLE retirement step. A braid, not a total order -- and collapsing
it back to a program order yields a different plan in either direction.

The ruling closed with an explicit instruction to carry the precedence
edges ONCE and not duplicate them as prose in both carriers, which is §3
applied to a fact with two natural homes: two copies are one fact with two
authorities, and they diverge on the first amendment. So
gunbc.compiler_frontend_program_interlock owns the relation and both plans
cite it; their prose renders it.

milestone_prerequisites is a TOTAL FUNCTION over a closed milestone
variant, not a list of edges. A list is satisfied by omission -- a
milestone nobody wrote an edge for silently has no prerequisites and the
failure is invisible. The exhaustive match makes an unstated prerequisite
fail to compile (§5, construction over validation). Same shape for the
admission predicate, which admits on UNADJUDICATED delta being empty
rather than delta being empty: expected cut motion may occur, unevaluated
motion may not. A wall demanding zero delta would refuse the cut itself and
then be repaired by weakening it.

Executable consequence recorded in the namespace plan: its disclosed "no CI
mechanism" gap becomes a BLOCKER gating Step 1 by name, with preparatory
work explicitly unaffected. Its section 7 stops being an open question and
becomes a projection of the carrier.

Two corrections from the operator's exact-head review:

- The ratchet clause said counts are "display only and decide nothing".
  The ratchet owner measured that as literally false. Replaced with their
  wording: no cardinality is a gate oracle, but emptiness decides whether a
  population is inhabited or evaluated -- including the distinction between
  an empty roster and an identified roster with no failures -- and the
  roster DIGEST, not its count, is its identity.
- The status block claimed "no transcribed instrument output" while the
  next section explicitly carries a transcription and names its missing
  producer. It no longer claims both states.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The build lane's parse phase refused:

  module index refused: 1 unparseable .dag source(s)
    dag/gunbc/plans/import_namespace_program.dag:8636-9127:
    expected expression, found Unknown

My error, and it is an escaping-layer mistake rather than a .dag one. I
authored the section through a Python here-doc and wrote \\' inside a
triple-quoted Python string to protect the apostrophe from PYTHON. Python
emitted a literal \' into the .dag file, where a double-quoted string needs
no escape for an apostrophe and \' is not a valid escape -- so the lexer
produced Unknown and the parser refused at the enclosing expression.

Four occurrences across three lines, all in the one file the index named;
the other two new modules parsed clean, which is why the refusal counted
exactly one source.

Worth noting the wall worked as designed: this was caught by the parse
sweep in the build lane, at the phase DESIGN records as sweeping src/v1,
dag and src/v2 from one roster, before anything downstream consumed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two findings, and the first splits rather than landing whole.

FINDING 1 -- predicate dissolution. The rule is real and I verified its
scope before acting: std.execution_mode records that the 2026-07-12
dissolution deleted SINGLE-VARIANT NICKNAMES (is_hermetic/is_record), and
that a predicate deciding a SEMANTIC PARTITION survives it --
execution_mode_is_wet_dispatch groups three variants into two because Wet
and Record share dispatch semantics, keeping one authority instead of an
inline match at every consumer.

  namespace_change_admitted_before_wall: two variants mapped one-to-one
  onto true/false. That is a nickname for a variant test. DELETED. Its
  distinction already lives in NamespaceChangeClass, which consumers match
  on, and the plan's citation is repointed to the type.

  delta_disposition_auto_admitted: nine dispositions partitioned three-to-
  six on whether the wall auto-admits them. That is the surviving shape,
  on the grounds std.execution_mode records by name. RETAINED, with the
  argument written beside it so the next reader does not re-litigate it.

FINDING 2 -- prose drift. Upheld. Section 7 enumerated both precedence
edges while asserting the carrier was their only home, which is worse than
either alone: a symbol citation verifies a declaration EXISTS and never
that prose about it still AGREES with it, so enumerated edges beside a
citation are precisely the drift single authority prevents. The prose now
renders the relation without restating it, says explicitly that it is not
authority for the edges, and points at milestone_prerequisites for the
gate condition instead of repeating it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…orrected

Two narrow semantic checks raised on 620a118.

1. The ruling gates the first namespace wave on S2 + S3 + S4. The closed
   milestone SelfHostCargoRatchetEnrolled named S3 and S4 and left the S2
   whole-corpus census implicit inside the phrase "proof envelope".

   That is a real gap rather than a naming preference, and the reason is
   worth stating because it is a limit of the construction this carrier
   leans on: the closed variant makes an omitted MILESTONE fail to compile,
   but it cannot make an omitted FACT fail to compile when that fact hides
   inside a composite name. Totality protects the enumeration, never the
   contents of an element of it. So a required precondition had become
   unenforceable in the very carrier built to enforce preconditions.

   Fixed by adding SelfHostWholeCorpusPopulationDerived as its own variant
   rather than by renaming the composite, since renaming would have left
   the fact implicit and merely better labelled. NamespaceFirstSemanticWave
   now requires all three.

   The general rule is recorded beside it: when a construction derives its
   guarantee from exhaustiveness, every fact the guarantee must cover has
   to be its own element -- a composite element is a place for a fact to
   hide from the check that makes the construction worth having.

2. Section 9 announced the ordering relation as retired and then closed by
   counting "Four gaps". Now three live gaps plus one retired question,
   with the retired bullet kept only so a reader of an earlier revision
   does not hunt for an answered question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deleted delta_disposition_auto_admitted, and the reason is independent of
the question it was reviewed under.

THE REVIEW'S STATED GROUND DOES NOT HOLD. The finding cites "the exact
predicate/walker-dissolution shape prohibited by DESIGN.md". DESIGN.md
contains zero occurrences of "walker", and its only predicate clauses say
that a general fn(T) -> Bool refinement does not lift to proof and that a
caller-supplied validator is defeasible -- both claims about the guarantee
ladder, neither a prohibition on Bool projections. The predicate-dissolution
rule is real but lives in the corpus, at std.execution_mode, and that row
states the surviving case explicitly: a two-variant semantic partition is
kept where a single-variant nickname is deleted, because deciding a
partition once keeps one authority instead of an inline match at every
consumer.

DELETED ANYWAY, ON A GROUND THAT DOES HOLD: it had no consumer. Measured --
the only reference in the tree was a DeclarationRef in this PR's own plan.
The wave-admission wall that would classify deltas does not exist yet, and
DESIGN section 6 names a new artifact with no final consumer as
experimental residue. A partition nobody computes over is a guess about
what a future consumer will want, and the surviving-partition argument
presupposes consumers that would otherwise inline the match. There are
none, so the argument does not apply to this predicate either.

The operator's ruled partition is preserved as an annotation, where it
cannot be mistaken for an executing mechanism, and the plan's citation is
repointed to NamespaceDeltaDisposition. The type stays: it carries the
ruled vocabulary and the wall will match on it directly. Bool is dropped
from the imports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
review 56376 found that SelfHostCargoRatchetEnrolled carried no prerequisite
while its own label named S4, and the plan defines S4 as enrolment against S2's
population. The typed interlock therefore permitted enrolling the ratchet before
the population it ratchets against exists.

The review offered two repairs. Adding S2 as a prerequisite to the fused variant
is the wrong one: S3 (enrol a cargo-executing phase) genuinely has no
prerequisite, so that repair closes the permissive half by introducing a
false-blocking half. The variant is composite, and its two halves have different
prerequisites, so a single prerequisite list must state either the minimum or the
maximum and both are wrong. The repair is the split.

This file's own annotation already stated the rule -- a composite element is a
place for a fact to hide from the exhaustiveness check that makes the
construction worth having -- and this instance was left standing in the same
diff that wrote it down. The annotation now carries the second instance, since a
rule with one instance reads as a repair and a rule with two reads as a rule.

Prerequisites are direct edges rather than the transitive closure: S2 is not
repeated on NamespaceFirstSemanticWave because S4 now carries it, so a later
correction to S4 cannot leave a stale duplicate standing (DESIGN section 2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…f one shape

loyal-lark-254 found that SelfHostRatchetEnrolled was itself composite. It fused
enrolling the ratchet as an OBSERVATION -- taken, persisted, never able to fail a
merge -- with enrolling it as a GATE. Their prerequisites differ: an observation
needs no population to be about, since what it refuses is the inability to take
or persist it; a gate is meaningless without the identity-grain population it
gates against.

Fused, the variant read as a prerequisite over both. That would have blocked
observation work an operator ruling had already authorised, via a carrier that
landed after the ruling -- a single-authority collision committed by the file
built to prevent them.

NamespaceFirstSemanticWave now depends on the GATE form, since what the namespace
plan says gates Step 1 is an enforcing mechanism over the import population, and
an observation cannot enforce.

Three instances of one shape in one file is the finding, not three repairs. Each
was a variant fusing two facts whose prerequisites differ, and each was invisible
to the exhaustiveness check that is this construction's whole reason for
existing. The annotation now carries the standing obligation that follows: the
match already forces prerequisites to be stated, so the question a new milestone
must answer is whether it carries two facts that would state DIFFERENT
prerequisites if separated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
loyal-lark-254 pointed out that a review reports one instance because it found
one instance -- a property of the reviewer's attention, never a census -- and
that the standing obligation this file had just written down should be RUN over
every variant rather than applied at the reported site.

Running it found NamespaceTerminalEndState fusing Steps 1-5 into one "terminal
end state". The namespace plan marks Step 5, the grammar and parse deletion, as
LAST, and gives the reason: deleting the grammar first makes every unrepaired
module unparseable at once, converting a fix-forward program into a flag day. So
the fused variant erased its own ordering constraint, and the constraint it
erased is the one that keeps the program survivable.

Split into NamespaceFixForwardComplete (Steps 2-4) and NamespaceGrammarRetired
(Step 5, downstream of it). Seed retirement is now downstream of the grammar
deletion rather than of a composite.

Four instances of one shape in one file, and only the last was found by census.
The first three were each reported by someone who had run into them. That is the
difference between a repair and a wall: repairing reported sites converges on
reviewer attention, enumerating the shape converges on the corpus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…struction

snappy-dove-250 measured what crisp-crab-430 is actually building rather than
reading its session title, and it is a PRE-DELETION BASELINE INSTRUMENT: a
content-addressed, past-tense record of what the legacy resolver actually
selected over one exact base. It must precede Step 1, because once the cut lands
that record is unrecoverable.

That is the strongest kind of ordering constraint there is -- violating it
destroys evidence rather than merely reordering work -- and it was not in this
carrier at all. The graph therefore showed an active, ungated session as blocked
on prerequisites its work does not have.

The four earlier findings in this file were FUSIONS, and a census over the
declared variants found the last of them. This one is an OMISSION and no such
census could have found it. Exhaustiveness forces prerequisites to be stated for
every milestone declared; it cannot force a milestone to be declared. So the
match makes an unstated prerequisite unwritable and leaves an unstated MILESTONE
invisible -- totality protecting the enumeration and not its completeness, one
level up from the rule this file already records.

The annotation states plainly that finding a missing variant requires joining
this file against the programs it claims to describe, that no mechanism performs
that join today, and that the annotation is not one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both lanes failed on ONE cause. The floor lane reported it as `FAILED PHASE
parse (8 error(s))` against the two plan carriers; the build lane reported it as
`v2-emission EmissionRefused ... produced 8 hard diagnostic(s)` against
src/v2/compiler/00_compile.dag. They are the same eight §4c violations, addressed
by line in one and by byte offset in the other.

The annotations sat inside `data ... = [ ... ]` list literals, labelling groups of
decl_ref rows. §4c admits only standalone leading `//` blocks attached to
module-scope declarations, so an annotation inside a declaration body refuses.
Each group label is hoisted into the leading block above its declaration, which
keeps the grouping legible without inventing a grain the realization does not
model.

The build lane's attribution is worth knowing before anyone chases it: an
annotation defect in dag/gunbc/plans/ surfaces as an emission refusal naming the
v2 compiler entry, because that entry's census sweeps the corpus. The named
subject is the entry, not the offending file; the offending file appears only in
the diagnostic payload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s own plan

review 56390 joined this carrier against the self-host plan and found that
SelfHostSeedRetirement depended on NamespaceGrammarRetired ALONE. The plan
requires two further conditions before S6: that the emitted Rust compiles, and
that behavioral equivalence to the seed is re-established. Neither existed as a
milestone, so the authoritative carrier permitted the one irreversible step in
either program on weaker conditions than the plan it claims to sequence.

Added as two milestones, not one, on the plan's own distinction: a rustc-clean
corpus permits S6 to be PLANNED, it does not AUTHORIZE it. Compiling is a
property of the emitted text; equivalence is a property of what that text does.
Fusing them would have been this file's characteristic defect committed while
repairing its mirror image.

This is the sixth defect of the same family and the second OMISSION. It is also
the first one found by the join the file's own annotation says nothing performs
-- a reviewer performed it. That is evidence for the stated limit rather than
against it: no census of this file could have surfaced this, because there was no
variant to enumerate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…inding deltas before a namespace change merges

gunbc.plans.import_namespace_program section 9 records that no CI mechanism
enforces any of it, and the 2026-08-26 ruling at
gunbc.compiler_frontend_program_interlock makes that a BLOCKER gating
NamespaceFirstSemanticWave on NamespaceWaveAdmissionEnrolled by name. This is
that milestone: a required namespace-wave-admission phase in
claim_executor --required-ci, witnesses lane, riding the parse sweep that
already runs.

The base index is the head index with the diff applied in reverse at file
grain, so only changed files are parsed twice while closure and bindings are
recomputed over both whole graphs. The change class is DERIVED from the
measured delta rather than declared by an author. The wall admits when the
UNADJUDICATED delta is empty, never when the delta is empty.

Closure is a pure function of membership, so an arm for "closure moved and
membership did not" would be permanently green by construction. It is measured
and attributed to its generators instead -- each delta names its blast radius.

The grain is authored containment identity (module, enclosing declaration,
LEAF segment), because v2.workflow.legacy_binding_delta establishes that no
admissible cross-compile occurrence key exists without a transformation to emit
one, and there is none between a merge base and a head. Row values are candidate
SETS, so shadowing widens a set rather than picking a winner. The leaf rather
than the whole spelling is a fixture finding: keyed on spellings, requalifying a
reference reads as NewUnresolvedness, and requalification is the namespace
program's own core motion.

The binding channel reads every authored name occurrence from the parse tree
rather than import members, so it does not go blind on the cut it gates.

Evidence: 14 fixture-boundary arms in tests/namespace_wave_admission.rs, every
refusing arm one mutation of the auto-admitted control, plus a two-directional
vocabulary join between the host enum and the .dag coproduct it realizes, whose
absent-authority arm refuses.

The admission roster is empty, which is the correct landing state; the coarse
class-admission carrier is deliberately not built, and the constraint on its
shape -- bounded by enumerated identity, never by predicate -- is recorded in
gunbc.namespace_wave_admission.

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

emit_main_rs produces 552 lines against the committed 1318. Absent from the
emitted form: the whole Ci subcommand, Converge, Serve, and --entry on compile.
Measured by execution 2026-08-21 and accepted then as a program-level correction.
No number of regenerations closes it.

It is a distinct milestone because it is invisible to both milestones beside it.
It never appears in a rustc error count, so SelfHostCorpusEmitsCleanly can be
fully satisfied while it stands -- errors-to-zero is necessary and not sufficient
-- and it is not a behavioral difference between two producers, so equivalence
does not reach it either. It is the absence of a producer, and neither of the
other two can express that.

Its executable home already exists: EmitterProducedDivergentRegistration in
v2.compiler.self_host.stage0_crate_layout, enforced in three directions so a row
cannot outlive its producer. The milestone is that no such row remains.

This is the seventh defect of one family in this file and the third omission, and
it indicts the method rather than extending the list: it was not discovered. It
was already known, recorded, and accepted as a correction to this very program a
week before this carrier was authored, and the carrier was still written without
it. The join that finds omissions is not merely unmechanized -- it is not
reliably performed even by someone holding the fact. None of the three omissions
was found by reading this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title NamespaceWaveAdmission: build the required wall over closure, subject-membership and occurrence-binding deltas that gates the namespace program's first semantic wave — the enforcement mechanism the plan names as a BLOCKER, dispatched OUTSIDE the program it enforces so it cannot grade its own homewor The wave-admission wall: adjudicate closure, subject-membership and binding deltas before a namespace change merges Aug 26, 2026
…fields and this one carried five

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 26, 2026 22:33
gunbc-ci-auto-heal and others added 6 commits August 26, 2026 22:36
… body: a body is not an authority

Records what the milestone delivers -- two adjudicated walls, a measured
closure blast radius attributed to its generators, and one declared stall --
so a reader of NamespaceWaveAdmissionEnrolled cannot take it for a third wall
over closure or for occurrence grain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the class admission arrives one grain down

Three facts measured against the carriers by the owning program's session
rather than against intent (snappy-dove-250, 2026-08-26):

A requalification wave prepends the declarer's path and leaves the declarer
fixed, so it changes the authored spelling and leaves the last segment
invariant BY CONSTRUCTION. This wall's key is therefore invariant under
exactly the operation the cut performs, and the reduction has a named
authority already: v1.05_emit_rust rust_fn_sig_leaf_name_dotted_note names
qualified_last_segment. Recorded as an invariant rather than as a finding,
because a finding invites re-litigation.

TargetChanged firing on a symbol move is signal, not tax: the owning program
splits requalification and relocation into separate diffs, so that refusal
reports a mis-shaped wave. No exemption, threshold or allowance is admitted.

LegacyBindingObservationRow carries only an occurrence identity and an
outcome, so a class admission must project down to this wall's key -- through
an authored_name that carries dots and reduces only through
qualified_last_segment, a trap that fails silently because on an unqualified
reference the spelling and the leaf are identical.

The grain note also said 'spelling' where the implementation keys on the
leaf; that is corrected here rather than left to drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…te, and argue the residue per declaration

The operator ruled that a request to add hand Rust is the occasion to migrate or
delete hand Rust. This change answers at DECLARATION grain rather than as a count,
and it does not buy the count down by shrinking the wall.

DELETED, measured rather than argued:

- `leaf_of` was a NICKNAME for `v1.00_core` `qualified_last_segment`, which
  `v1.05_emit_rust` `rust_fn_sig_leaf_name_dotted_note` names as the single
  authority for taking an authored spelling to its last segment. The wall now
  calls that mirror. This is the stronger construction as well as the smaller
  one: the reduction the wall keys its rows on is now the corpus authority
  instead of a local respelling of it.
- `render_set`, inlined at its two call sites.
- `claim_executor` carried a BYTE-IDENTICAL PRIVATE COPY of `git_stdout`. The
  wall needed the same helper, so rather than land a third spelling the lib owns
  the one copy and the bin's is deleted. Net minus one PRE-EXISTING hand
  declaration.

Four inherent methods became free functions, because `std.decl_ref` offers only
`WholeDeclaration` and `NamedField` -- a method on an impl block is UNCITABLE, so
as methods they were items the seed-growth roster structurally could not
enumerate. The module now carries no impl block.

The roster went 25 -> 33 rows, which is the honest direction: it was JOINED
against the module's actual declaration population instead of listed from
memory, and six declarations were missing from the first draft. `gunbc.seed_growth`
already warns that `hand_authored_declarations` is an authored obligation roster
rather than a derived denominator; this is that warning firing on its own first use.

The residue is argued per class, not asserted as irreducible. Class A (28
declarations) is a pure fold whose INPUT TYPE is host-only: `ModuleDeclarationRecord`
and `DeclarationIndex` have no `.dag` carrier, and authoring one here would be the
second representation `gunbc.declaration_index_seed_growth` already owns -- so this
roster follows that carrier's first step rather than forking it. Class B (4
declarations) is blocked on a typed repository-read transport, which is the scm
lane's subject.

Verified: `cargo check -p v1-compiler --bin claim_executor` clean, 14/14 fixture
arms pass, `cargo fmt --all --check` clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`claim_executor` imported `std::path::Path` for its own copy of `git_stdout`, which
the previous commit deleted in favour of the lib's. Bare `Path` was then reachable
only from one `#[cfg(test)]` site, and the build lane runs with `-D warnings`, so
`unused_imports` is an ERROR there and both lanes refused.

The import is narrowed to `PathBuf` and the single test site spells the type in full,
matching the ten other `std::path::Path` uses already in the file.

WHY MY OWN CHECK DID NOT SEE IT, recorded because the check was not wrong, it
answered a different question: I verified with `cargo check -p v1-compiler --bin
claim_executor` and no RUSTFLAGS. The gate runs `cargo build --release -p v1-compiler
--bins` with `-D warnings` -- more targets AND warnings promoted -- so a green check
under weaker flags is not evidence about the gate. Any DELETION of host Rust can
strand an import, so the deletion half of the operator's directive is exactly where
this class fires.

Verified: RUSTFLAGS="-D warnings" cargo check -p v1-compiler --bins, fmt clean.

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

Review 56411 (non-blocking) noticed that folding the two copies together left the
function on `trim_end` where the private copy used `trim`. The behaviour is right;
the doc comment above it still said "trimmed stdout", which is the sentence a future
caller reads before choosing this helper.

The asymmetry is deliberate and now says so: one caller asks for the CONTENT OF A FILE
at a ref, and a `.dag` module whose first line is indented would arrive with that
indentation eaten -- a different file from the committed one, compared against a head
side read from disk intact.

Checked per caller rather than asserted: `rev-parse`, `merge-base` and
`diff --name-only` carry no leading whitespace; `status --porcelain` re-trims at both
of its use sites AND is the one worth noticing, because its lines BEGIN with the
two-column XY code -- a leading `trim` would have corrupted a status read rather than
tidied it. So the pre-existing caller is not merely unharmed by the change, it is
better served by it.

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

Main moved three times while this PR sat at the approval floor and #9352 landed as a
SQUASH, so this branch carried its original commits against main's flattened one.

THE SPLIT IS TWO AUTHORED AND THREE GENERATED, not one and four. `claim_executor.rs`
LOOKS like a stage0 mirror by its path and is not: it has no `// Generated by v1
compiler` header and no `.gitattributes` entry, while a real mirror announces itself.
Nothing generates it, so waiting for a generator to resolve it would have waited
forever.

AUTHORED, resolved by hand:
- `seed_growth_admission.dag` -- both sides ADDED a roster entry, so it is a union
  rather than a choice: mine plus main's `target_invocation`, import list re-sorted.
- `claim_executor.rs` -- both sides DELETED different things in one region. Main
  deleted two test mods whose production symbol `witness_walk_flags` no longer exists;
  this branch deleted the duplicate `git_stdout`. Verified against main that neither
  mod survives and only `git_stdout` sat between the surrounding braces, so the region
  collapses to nothing.

GENERATED, resolved by regenerating -- the driver REFUSES these rather than picking a
side, and it was right to: the ours side carried this branch's `namespace_wave_admission`
row and had silently DROPPED main's `target_invocation_host`. Taking either side loses
the other's authority-derived content with no conflict to show for it.

`--required-regen` named exactly one drifted file, `gunbc_stage0_crate_layout_generated.rs`,
installed from the candidate tree; the confirming pass ran a binary REBUILT FROM THE
INSTALLED SEED and reported `first_generation_equal=true` (the single
`declared_divergent [main.rs]` is the standing declared divergence). One pass alone can
self-verify at divergence 0 for the wrong reason, which is why the rebuild is not
optional.

TWO MAIN-DRIFT FACTS SURFACED AND ARE DELIBERATELY NOT LANDED HERE. `DESIGN.md` is
registered `merge=generated-artifact`, but #9366 edited DESIGN.md alone with no `.dag`
authority change -- its new 4b(3) clause exists in no module under `dag/` or `src/v2/`
-- so a faithful regeneration REVERTS it. Separately the generator adds three
`.gitattributes` rows main lacks. Both files are restored to the merge result: #9366's
clause is preserved here, and repairing the underlying drift belongs to whoever owns it
rather than buried in a wall PR.

Verified: RUSTFLAGS="-D warnings" cargo check -p v1-compiler --bins clean, 14/14
fixture arms pass, fmt clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings in review 56449 are confirmed by reading the code, and my own
doc comment on `base_records` carried the erroneous rationale verbatim -- "that
is the conservative direction: it can only make the wall quieter" -- which is
exactly the empty-observation narrow DESIGN names: bottom-as-answer conflated
with bottom-as-ignorance, strictly worse than the widen section 5 forbids,
because a widen is merely expensive and a narrow is silently uncovered.

Two fixes, one per finding:

`base_records` returns `Result<Vec<ModuleDeclarationRecord>, String>` and
refuses when the base-side parse produced any diagnostic. A source that does
not parse at the base revision has no readable declarations; it does not have
zero of them. Reading it as empty made every declaration in the head revision
look new, so the wall adjudicated a baseline it had never observed.

The base-side loop establishes absence from `git ls-tree -r --name-only <base>`
-- an authoritative listing of what the base revision contains -- rather than
inferring it from a `git show` failure. A path missing from that listing was
genuinely added by this change; a path present in it whose content cannot be
read, or whose content does not parse, now returns `NotEvaluated` with the
reason, so "could not compare" can never render as "nothing changed".

Mutation receipt, one mutation, restored between: baseline 15/15 green;
revert `base_records` to `Ok(Vec::new())` on the diagnostic arm; 14 passed,
1 failed, and the one red is
`a_base_side_source_that_does_not_parse_refuses_instead_of_reading_as_empty`
-- it fails alone. Restored, 15/15 green, `--bins` clean under `-D warnings`
with `forwarding env: RUSTFLAGS` confirmed on the dispatch. The new arm carries
its own positive control on the same function: a well-formed baseline still
reads a non-empty record set, so the arm cannot pass by refusing everything.

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

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Review 56449 (codex/gpt-5.6-sol, REQUEST_CHANGES) — both findings confirmed and fixed in fdf66e2.

They were correct, and the rationale for the defect was sitting in my own doc comment on base_records: "that is the conservative direction: it can only make the wall quieter." That is exactly backwards, and it is the empty-observation narrow DESIGN names — ⊥-as-answer conflated with ⊥-as-ignorance. A widen is merely expensive; a narrow is silently uncovered.

Finding 1 — base-side parse diagnostics read as an empty record set. base_records now returns Result<Vec<ModuleDeclarationRecord>, String> and refuses when the base-side parse produced any diagnostic. A source that does not parse at the base revision has no readable declarations; it does not have zero of them. Reading it as empty made every head-side declaration in that file look new, so the wall adjudicated a baseline it had never observed.

Finding 2 — every git show failure treated as proof of a new path. Absence at base is now established distinctly, from git ls-tree -r --name-only <base> — an authoritative listing of what the base revision contains. A path missing from that listing was genuinely added by this change. A path present in it whose content cannot be read, or whose content does not parse, returns WaveAdmissionOutcome::NotEvaluated carrying the reason, so "could not compare" can never render as "nothing changed".

Mutation receipt (one mutation, restore between): baseline 15/15 green → revert base_records to Ok(Vec::new()) on the diagnostic arm → 14 passed; 1 failed, the single red being a_base_side_source_that_does_not_parse_refuses_instead_of_reading_as_empty. It fails alone. Restored: 15/15 green, --bins clean under -D warnings with forwarding env: RUSTFLAGS confirmed on the dispatch. The new arm carries a positive control on the same function — a well-formed baseline still reads a non-empty record set — so it cannot pass by refusing everything.

gunbc-ci-auto-heal and others added 2 commits August 27, 2026 02:39
Review 56459 (non-blocking) found a 26-space run mid-string in the
namespace-wave-admission NOT RUN message. It was a line-wrap artifact rather
than deliberate spacing, and it reaches the operator's terminal verbatim, so
the one diagnostic a reader sees when the phase declines to adjudicate was
the least legible line the phase can print. Repaired with a `\` continuation
so the rendered text reads `did not produce an index`; the wording, the
`phase_failures` push and the fail-closed behaviour are untouched.

Checked with the build lane's own flags: `cargo check -p v1-compiler --bins`
under `-D warnings`, with `forwarding env: RUSTFLAGS` confirmed on the
dispatch, since a check weaker than the gate on either the target set or the
warning level carries no information about the gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 56471 is correct. `run_required_wave_admission` read
`git diff --name-only` as BOTH sides of the comparison, and rename detection
is on by default, so a detected rename is reported as its destination alone.
The source path therefore never entered the base-side re-read -- it is not in
the list -- and the destination is absent from the base tree, so the module's
entire baseline vanished: every declaration in it read as newly added, and a
required wall could refuse an ordinary `.dag` rename over a delta it had
invented. That is the same class as the two defects review 56449 found, one
level out: an observation that could not express what happened rendered as a
verdict about what happened.

`diff_sides` reads `git diff --name-status -z -M` and routes each entry to the
two sides SEPARATELY: a rename contributes its destination to the head-touched
set and its source to the base side, an addition contributes only a head path,
a deletion only a base path, and an ordinary modification the same path to
both. Scope is applied per side, because a rename can cross the sweep boundary
in either direction and the two sides must be measured by one instrument.

The parse is a pure function over the diff text, so the RED is authorable at
the fixture boundary and is authored there rather than argued in a comment.
Mutation receipt, one mutation, restored between: baseline 16/16 green; revert
the rename arm to push the destination onto both sides -- exactly the shape
`--name-only` forced on the old code -- and the result is 15 passed, 1 failed,
the single red being the rename arm. It fails alone, and the A/D/M controls
beside it stay green under that mutation, so the arm discriminates the one
distinction the review named rather than asserting the parse's shape.
Restored: 16/16 green, fmt clean, `--bins` clean under `-D warnings` with
`forwarding env: RUSTFLAGS` confirmed on the dispatch.

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

gunbai-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Review 56471 (codex/gpt-5.6-sol, REQUEST_CHANGES) — confirmed and fixed in 7b135fd.

The finding is right, and it is the same class as the two in review 56449 one level out: an observation that could not express what happened rendered as a verdict about what happened. run_required_wave_admission read git diff --name-only as both sides of the comparison. Rename detection is on by default, so a detected rename is reported as its destination alone — the source path never entered the base-side re-read (it is not in the list) and the destination is absent from the base tree. The module's entire baseline vanished, every declaration in it read as newly added, and a required wall could refuse an ordinary .dag rename over a delta it had invented.

Fix. diff_sides reads git diff --name-status -z -M and routes each entry to the two sides separately: a rename contributes its destination to the head-touched set and its source to the base side; an addition contributes only a head path; a deletion only a base path; an ordinary modification the same path to both. Scope is applied per side, since a rename can cross the sweep boundary in either direction and the two sides must be measured by one instrument.

Mutation receipt (one mutation, restore between). The parse is a pure function over the diff text, so the RED is authorable at the fixture boundary and is authored there rather than argued in a comment. Baseline 16/16 green → revert the rename arm to push the destination onto both sides, exactly the shape --name-only forced on the old code → 15 passed; 1 failed, the single red being a_rename_contributes_its_source_to_the_base_side_and_its_destination_to_the_head_side. It fails alone, and the A/D/M controls beside it stay green under that mutation — so the arm discriminates the distinction the review named rather than asserting the parse's shape. Restored: 16/16 green, fmt clean, --bins clean under -D warnings with forwarding env: RUSTFLAGS confirmed on the dispatch.

Also in this push: 450f351 repairs the whitespace nit from review 56459 — a line-wrap artifact left a 26-space run inside the namespace-wave-admission NOT RUN message, which reaches the operator's terminal verbatim.

— sent from nimble-moth-416

@briansrls
briansrls merged commit 8cc9099 into main Aug 27, 2026
3 checks passed
@briansrls
briansrls deleted the session/nimble-moth-416 branch August 27, 2026 04:23
briansrls added a commit that referenced this pull request Aug 27, 2026
…ut of NewPoolCoincidenceResolution (#9495)

* `0 -> 1` was two states: split AuthoredReferenceResolution out of NewPoolCoincidenceResolution

The wave-admission wall (#9365) classified every binding whose candidate set went
from empty to one member as `NewPoolCoincidenceResolution`, refusing unless an
operator admission names it. `binding_disposition` compares candidate SETS only,
and its own header says that arm means "a name that began denoting something
WITHOUT ANYONE AUTHORING A REFERENCE — resolution arriving from a pool".

That is one of two states sharing the symbol, and they have opposite owners and
opposite repairs:

  - the TARGET grew a name this module was already reaching for — the coincidence
    the containment rule exists to remove, cause in another module;
  - this module's own author wrote the import that resolves a name it was already
    spelling — the exact repair the wall was built to want.

The wall was refusing the second while naming it the first. Found by gunbc#9485,
a one-line import repair (5 blocking diagnostics -> 0) that the wall would not let
merge, and it could not be admitted either: `NAMESPACE_TRANSITION_ADMISSIONS` is a
`const &[TransitionAdmission]` over a `DeltaSubject` carrying `String` fields, so a
row naming an actual module is E0015 — that half is escalated separately and is
not repaired here.

The discriminator is the module's own source, and it was already in hand: the
membership arm one function over consults `membership_supported`, which admitted
the membership edge of the very change whose bindings were refused.
`locally_authored_claim_added` answers it from the two `ModuleDeclarationRecord`s
alone — deliberately index-free, because consulting either index reintroduces the
conflation (a blanket import whose target grew the leaf would read as authorship).
False is the fail-closed answer: it leaves the delta on the refusing arm.

The new disposition auto-admits, amending the 2026-08-26 operator partition on
`gunbc.compiler_frontend_program_interlock` per the 2026-08-27 ruling relayed
through swift-badger-524. The `.dag` coproduct is the authority and the host enum
is one realization; the vocabulary-join tests (host <-> ruling, both directions)
gate the pair and pass.

EXECUTED EVIDENCE. The test file asserted `NewPoolCoincidenceResolution` over a
head that authored `import probe.home { widget }` — it planted the repair and
named it the coincidence, which is why the conflation survived review. That
fixture is re-derived as the authored arm, and a real coincidence is authored for
the other: consumer source byte-identical across sides, reaching the target
through a blanket import, target grows the name. Both arms mutation-checked:
forcing `locally_authored_claim_added` true reds the coincidence arm alone,
forcing it false reds the authored arm alone. 20/20 green restored.

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

* Scope the blanket-import arm to the leaf: an unrelated new blanket import was laundering a pool coincidence

Review 56882 on this PR, before it merged. `locally_authored_claim_added`'s
member-list and self-declaration arms name the leaf in the source and were scoped
to it; the blanket-import arm names no leaf at all, and the first revision admitted
authorship whenever ANY blanket target was new. So an unrelated new blanket import
elsewhere in the same module auto-admitted a genuine pool coincidence beside it —
a fail-open widening, in the one direction this function must never move, and
directly opposite the docstring's own "false is the fail-closed answer".

THE ARM IS NOW A CONJUNCTION AND THE ORDER OF ITS HALVES IS THE POINT: the claim
must be NEW IN THIS MODULE'S SOURCE **and** the head target must actually supply
the leaf. Asking the second alone is exactly the conflation this function exists to
discriminate — an unchanged blanket import whose target grew the leaf would read as
authorship. Gating it behind the first makes the index consultation safe rather
than forbidden: a claim the author did not write cannot reach the surface check,
whatever the target did. So the docstring's index-free claim is corrected rather
than restated; it was true of the leaf-named arms and never of this one. A target
absent from the index answers false.

EXECUTED EVIDENCE, both directions of the scoping:

  - an_unrelated_new_blanket_import_does_not_launder_a_pool_coincidence — the
    reviewer's scenario. `probe.home` grows `widget` with its blanket import
    UNCHANGED; the author adds a blanket `probe.other`, which does not supply
    `widget`. Must stay NewPoolCoincidenceResolution and must stay refused.
    Restoring the leaf-blind arm reds this test ALONE.
  - a_new_blanket_import_that_does_supply_the_leaf_is_an_authored_reference_resolution
    — so the scoping is not a narrowing to nothing: when the NEW blanket target is
    the one supplying the leaf, it auto-admits like any other authored reference.

22/22 green, fmt clean.

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

* extdeps.container.oci.manifest uses three media_types symbols it never imported (#9485)

`accept_image_manifest` reads `oci_image_schema_version`,
`media_type_oci_image_manifest_v1` and `media_type_oci_image_config_v1`, all
declared in `extdeps.container.oci.media_types`, with no import of that module —
five BLOCKING name-resolution diagnostics standing on main. Adds the one import.

This is a live specimen of the DESIGN row declaring the blocking-diagnostic
population uncounted and unbounded: no required phase emits over a closure
reaching this module, so nothing refused.


Claude-Session: https://claude.ai/code/session_01TrqmnrTJvHxHxJuJFBED8k

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 29, 2026
…ge chain, deletion-subject roster, derived wave-wall enrolment

The manager ledger said the wave-admission wall was unbuilt while #9365 had
landed it as a required phase. This makes the control plane derive instead of
declare:

- gunbc.required_ci_phase_roster: substrate authority for required-CI phase
  identity and lane ownership (the census's own named next rung). The workflow
  emission consumes its lane vocabulary (fabric_witness_run lane words are now
  derived), and every --required-ci run joins the host RequiredCiPhase enum
  against the declaration's variant set in both directions and refuses on
  divergence (claim_executor phase_roster_findings, the vocabulary_findings
  shape). Lane-match parity is declared residue under the census's
  atomic-deletion trigger.
- gunbc.namespace_cut_stage: the NAMESPACE-XL serial chain (F-0, XL-0..XL-7,
  ACT-0) as a closed variant with TOTAL stage_prerequisites and
  stage_milestone_prerequisites (ACT-0 carries the interlock triple, by
  operator ruling), plus the activation-receipt gate carrier.
- gunbc.namespace_cut_subject_roster (+ live sibling
  gunbc.namespace_cut_subject_observation): every authority scheduled to
  disappear as string-identity probes (authored decl_ref literals would refuse
  at ingestion the moment a wave deletes a target), each with a total
  observation rule: Present/Absent by runtime declaration-ref resolution,
  Unobservable (typed, with trigger) for host Rust items and ambiguity.
  Executed: all substrate probes PRESENT, per-root acquisition after a
  measured union-walk OOM.
- compiler_frontend_program_status: NamespaceWaveAdmissionEnrolled derived
  from the phase roster (CLEAR); NamespaceBaselineObservationComplete
  Outstanding from the capture capability's blocked reasons; the three
  deletion milestones derived from the subject roster at declared-row grain;
  the stage chain rendered in where_are_we with standings and startability;
  instrument roster updated (three rows retired by delivery, cargo-execution
  fact and XL-3 spelling census added, conditionally-awaited roster for the
  CaptureAvailable contract).
- plan repoints: import_namespace_program section 9 gap closed by carrier
  citation; namespace-cut-replacement-plan.md carries a current-state
  authority banner instead of stale enrolment prose.

Verified by execution (BuildBuddy + local): parse sweep 4328 files clean;
where_are_we renders the exit shape (wall CLEAR, baseline OUTSTANDING, later
stages blocked on named prerequisites); subject_roster_report green in 1m49s;
falsifiers executed mutate/red/restore/green (roster row removal reads
Outstanding; planted stage and subject variants refuse to compile at every
total match); status witnesses 30/30 PASS, observation witnesses 4/4 PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpYEk9gSYmMkwL1489D79U
briansrls pushed a commit that referenced this pull request Aug 30, 2026
…ge chain, deletion-subject roster, derived wave-wall enrolment (#9698)

* F-0: truthful namespace control plane — phase roster, NamespaceCutStage chain, deletion-subject roster, derived wave-wall enrolment

The manager ledger said the wave-admission wall was unbuilt while #9365 had
landed it as a required phase. This makes the control plane derive instead of
declare:

- gunbc.required_ci_phase_roster: substrate authority for required-CI phase
  identity and lane ownership (the census's own named next rung). The workflow
  emission consumes its lane vocabulary (fabric_witness_run lane words are now
  derived), and every --required-ci run joins the host RequiredCiPhase enum
  against the declaration's variant set in both directions and refuses on
  divergence (claim_executor phase_roster_findings, the vocabulary_findings
  shape). Lane-match parity is declared residue under the census's
  atomic-deletion trigger.
- gunbc.namespace_cut_stage: the NAMESPACE-XL serial chain (F-0, XL-0..XL-7,
  ACT-0) as a closed variant with TOTAL stage_prerequisites and
  stage_milestone_prerequisites (ACT-0 carries the interlock triple, by
  operator ruling), plus the activation-receipt gate carrier.
- gunbc.namespace_cut_subject_roster (+ live sibling
  gunbc.namespace_cut_subject_observation): every authority scheduled to
  disappear as string-identity probes (authored decl_ref literals would refuse
  at ingestion the moment a wave deletes a target), each with a total
  observation rule: Present/Absent by runtime declaration-ref resolution,
  Unobservable (typed, with trigger) for host Rust items and ambiguity.
  Executed: all substrate probes PRESENT, per-root acquisition after a
  measured union-walk OOM.
- compiler_frontend_program_status: NamespaceWaveAdmissionEnrolled derived
  from the phase roster (CLEAR); NamespaceBaselineObservationComplete
  Outstanding from the capture capability's blocked reasons; the three
  deletion milestones derived from the subject roster at declared-row grain;
  the stage chain rendered in where_are_we with standings and startability;
  instrument roster updated (three rows retired by delivery, cargo-execution
  fact and XL-3 spelling census added, conditionally-awaited roster for the
  CaptureAvailable contract).
- plan repoints: import_namespace_program section 9 gap closed by carrier
  citation; namespace-cut-replacement-plan.md carries a current-state
  authority banner instead of stale enrolment prose.

Verified by execution (BuildBuddy + local): parse sweep 4328 files clean;
where_are_we renders the exit shape (wall CLEAR, baseline OUTSTANDING, later
stages blocked on named prerequisites); subject_roster_report green in 1m49s;
falsifiers executed mutate/red/restore/green (roster row removal reads
Outstanding; planted stage and subject variants refuse to compile at every
total match); status witnesses 30/30 PASS, observation witnesses 4/4 PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpYEk9gSYmMkwL1489D79U

* Adjudicate the RequiredCiLane move: two exact TargetChanged admissions

The required namespace-wave-admission phase refused #9698 with exactly two
unadjudicated deltas: BuildLane and WitnessesLane in the census's
required_ci_host_verdict_rows now bind through gunbc.required_ci_phase_roster
instead of the census itself — the declaration move this PR performs on
purpose, at the census's own named next rung. Two exact-identity admission
rows adjudicate them, per the roster's rule (enumerated identity, never a
predicate). They go stale when this PR merges and are owed removal in the
follow-up, exactly as the roster's three prior shrinks record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpYEk9gSYmMkwL1489D79U

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <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