Skip to content

Read the base revision under its own grammar: the parse environment loader and the wave-admission partition - #10970

Merged
gunbai-bot[bot] merged 25 commits into
mainfrom
session/cool-ibex-701-p1
Sep 13, 2026
Merged

gunbai-bot[bot] merged 25 commits into
mainfrom
session/cool-ibex-701-p1

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

What

The consumer for ParseEnvironment (#10948). The namespace-wave-admission gate now reads the base revision's declarations under the base revision's grammar, instead of the head compiler's.

This is the repair for gunbc.recurring_failure_mode.base_readability_gate_refuses_a_grammar_change: a gate that parses both sides with one compiler has no arm for "the base is well formed under its own grammar and unreadable only under the head's", so a change that edits the grammar is refused in proportion to how thoroughly it succeeded. #10850 is the live specimen — its floor lane currently reports namespace-wave-admission NotEvaluated — dag/extdeps/access/posix_effective_principal_read_op.dag does not parse at the base revision (5 diagnostic(s)), the exact file and count recorded in the class row.

How

The loader (cli_run::namespace_wave_admission::load_parse_environment_at) reads the environment's declaring closure at a git revision — one git archive into target/, then the repository's own module index and resolver — and evaluates dag_parse_environment under Hermetic, selecting the declaration by exact ownership rather than a bare-name lookup a homonym could hijack. Decode is Value → value_to_wire_json → serde_json::from_value, because the wire encoder resolves its tag policy from the same emitter that wrote the mirror struct's #[serde] attributes: no hand-written decoder, no second authority for the type's shape.

The partition, which is the correctness core. environment_agreement(base, head) asks the resolver for the closure's files (13 today; the number appears nowhere) and compares object ids:

  • identical → equal blobs are equal content, so the grammars agree by construction and the existing changed-file baseline reconstruction stays valid. The ordinary PR pays a handful of rev-parse calls.
  • differs → the base environment is loaded and the reconstruction is abandoned: carrying untouched head records assumes an untouched file has an untouched parse, which is exactly false once a keyword moves. Every base-side file in sweep scope is re-read under the base's grammar.

Kernel guard: blob equality on dag/std/types.dag, else NotEvaluated — declaring_candidates consults this binary's head-derived kernel_type_set, and an open question refuses rather than guesses.

Authority side: parse_census_fill_sources_with(sources, retention, environment) is one fold on both axes (main's retention axis from #11121 and this branch's environment axis met in the merge); three wrappers each vary one. base_records requires an environment — a forgetful base caller stops compiling rather than silently reading head rules.

Every failure arm refuses; none substitutes the head's environment, which would fail open on exactly the changes this exists for.

Evidence

  • Revision-provenance probe: a scratch repo with the worktree diverged from the commit; the loader must return the committed grammar. Fails if it reads the worktree or ignores its revision argument.
  • Decode oracle: the loader over the live tree reproduces the compiled-in dag_parse_environment() whole-value, with a non-vacuity control on the oracle.
  • 51 wave-admission tests + 3 loader tests pass; clippy --all-targets -D warnings clean; regen first_generation_equal=true with sha256s confirmed on the build host.
  • In CI on the prior head, the modified gate itself reported namespace-wave-admission ADMITTED over 5,589 modules.

Incidental, found by the probe and fixed: serialize_variant_to_wire_json called the attribute-question tag function on the data path, so every plainly-tagged coproduct failed to serialize; it now calls data_path_wire_variant_tag, which the corpus already carried with an annotation stating exactly this distinction.

Landing

No srv2 closure receipt is carried, so the receipt-currency rule has nothing to stale. Merged with post-#10940 main; the wave-admission file passed the zero-deletion check — all 19 removed main lines are code this change deliberately rewrites. The outstanding floor red on recent heads has been MemoryStallRefusedPageThrash, which the workflow itself classifies as infra.

#10850 follows this: its single remaining red is the dependency.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NQnvBvFGNu9tNL544NJe2j

gunbc-ci-auto-heal and others added 3 commits September 10, 2026 21:02
`ParseEnvironment` is threaded through the tokenizer and parser (#10948),
but nothing could produce one other than the head's compiled-in
`dag_parse_environment`, so the thread had no consumer and a base revision
was still read under head grammar. This is the producer.

`cli_run::base_parse_environment` walks the environment module's transitive
import closure AT A GIT REVISION -- reading the object store, not the
worktree, because the point is a revision that is not checked out -- and
evaluates `dag_parse_environment` out of it.

THE CLOSURE IS WALKED, NOT LISTED. A hardcoded roster of its members would
be a second authority for what the environment depends on and would go
stale silently the first time `std.syntax` gained an import. It is 13
modules today; that number appears nowhere in the code.

EVERY FAILURE ARM REFUSES, NONE SUBSTITUTES. The tempting arm when a base
environment cannot be read is to fall back to the head's -- the exact
assumption this module removes, failing open precisely on the changes that
alter the grammar. Five typed refusals instead, `ValueNotDecodable` kept
separate from `ClosureNotEvaluable` because their owners differ: one is a
defect in the revision being read, the other is this binary disagreeing
with its own emitter.

DECODE IS NOT HAND-WRITTEN. `Value` -> `value_to_wire_json` ->
`serde_json::from_value`, because the wire encoder resolves its tag policy
from the same emitter that wrote the `#[serde(...)]` attributes on the
mirror struct. A hand-written decoder would fork the type's shape across
nine types and drift the first time a field was added to `SyntaxSpec`.

INCIDENTAL FIX, FOUND BY THE PROBE AND NOT PREDICTED.
`serialize_variant_to_wire_json` called `wire_variant_tag_for_policy` on
the DATA path. That function answers the ATTRIBUTE question -- does this
variant need a per-variant `#[serde(rename)]` -- and its `none` means "no
attribute needed", which the caller read as "no tag exists". Every variant
of every plainly-tagged coproduct failed to serialize;
`BodyKind::AliasBody` is simply the first one reached. The corpus already
carried the right function, `data_path_wire_variant_tag`, whose annotation
states exactly this distinction. All three sites now call it, which also
deletes a hand-rolled fragment of the same rule in the final branch.

That branch previously never failed: it defaulted to the raw variant name
whenever no tag was derivable. So this also turns a silent widen into a
refusal, and gives a SCREAMING_SNAKE_CASE coproduct its correct tag rather
than the authored spelling -- a behavior change beyond the repair, and the
reason the wider suites are the gate rather than the new test alone.

Verified: the loader reproduces the compiled-in environment whole-value
from source, with a non-vacuity control pinning the oracle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZZi3QsDvibJqy8k8PwzZ9
Regen refused the new file: `src/v1/stage0/src/` IS the generated mirror
surface, so a hand-written file there reads as a committed mirror the
emitter no longer produces. That refusal was correct, and the two ways to
satisfy it are not equivalent.

Rostering `base_parse_environment.rs` in `seed_retained_intrinsic_registrations`
would have meant editing a v2 self-host authority and filing a seed-growth
justification row, because new hand-Rust in the frozen v1 seed is SEED
GROWTH admitted under a PURPOSE test -- for a function whose only caller is
`base_records`, twenty lines away in a file that is already rostered and
already carries its own seed-growth justification. That is growth for its
own sake, which is what the freeze exists to resist, and section 3 puts the
fact in its layer independently: this is part of the wave-admission gate's
realization, not a new seam.

So the loader moves into `namespace_wave_admission.rs`. The offending file
stops existing rather than being exempted, which dissolves the failure class
instead of excusing one instance of it.

Checked before writing that row rather than after: the git reads are not
re-invention of a modeled capability. `extdeps.git.object_store` models the
WIRE -- ls-tree authorities, validating object-id constructors, tree-entry
decoding -- while invocation is a Rust host boundary already standing at 32
sites across 8 seed files, `namespace_wave_admission.rs` among them.

Verified: clippy --all-targets -D warnings clean, the loader still
reproduces the compiled-in environment from source, regen
first_generation_equal=true.

STILL OUTSTANDING, and the loader is a declared frontier until they land:
`parse_census_fill_sources` and `parse_with_table_in_occurrence_scope` both
still pin `env: dag_parse_environment`, `base_records` does not yet take an
environment, and no cross-revision read is green by execution.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HOLD at exact head fb57d40c155ce9831c1f262c7bc7dd55dea27acd. Finish this PR as the actual prerequisite; do not split another producer-only PR.

The direction is right: #10948 made ParseEnvironment a required input, this branch can evaluate and decode that value, and the whole-value current/current control found a real wire-tag defect. But this head is still loader scaffolding, not revision-relative base acquisition, and cannot yet unblock #10850.

1. The revision read and the evaluator are disconnected

environment_closure_at(revision) reads Git objects. evaluate_environment_in(root, revision) evaluates whichever directory the caller supplies. No function materializes the former into the latter or otherwise joins them. In evaluate_environment_in, revision affects only refusal text; it does not select bytes. The new test calls evaluate_environment_in(workspace_root(), "HEAD"), so a loader that completely ignores the Git revision still passes.

At the production seam, base_records(rel, content) is unchanged and still calls parse_census_fill_sources(...), whose .dag authority and generated mirror pin dag_parse_environment(). run_required_wave_admission never calls the new loader. Thus the exact defect line remains live.

Build one production entry such as load_parse_environment_at(workspace, revision) whose output is demonstrably derived from that revision's blobs, and call it from run_required_wave_admission. Remove the disconnected/dead half rather than leave two ways to obtain an environment.

2. Thread the environment through the census path, at its authority

Add an explicit form in src/v1/compile.dag, then regenerate:

parse_census_fill_sources_with_environment(sources, environment)
parse_census_fill_sources(sources) =
    parse_census_fill_sources_with_environment(sources, dag_parse_environment)

base_records must take the observed base environment and call only the explicit form. No base-side route may fall back to the current wrapper.

The environment value must be selected by exact (module, declaration) identity. The current eval_data_item_value(&ctx, ENVIRONMENT_ITEM) is a bare-leaf registry lookup even though the code separately declares ENVIRONMENT_MODULE; a homonymous data item in the closure may not choose the parser's authority. Evaluate the static environment under Hermetic, not Wet; a historical data closure has no reason to gain host effects while being decoded.

3. An environment change invalidates changed-file-only reconstruction

The current wall carries every untouched HEAD record into the BASE and reparses only diff paths. That is valid only while the two parse environments are equal. A keyword, literal, operator, or item-form change can alter an untouched file's observation.

Required split:

base_environment == head_environment
    => retain current reverse-diff reconstruction

base_environment != head_environment
    => read and parse the complete base population in DAG_PARSE_SWEEP_ROOTS
       under base_environment

The full-base route is the rare grammar-change path. Add a fixture where an untouched module changes observation under the two environments; inheriting its HEAD record must make the control fail.

4. Replace the 5,306-process discovery shape

revision_module_map lists the whole dag/ tree and then runs git show once per .dag file merely to locate the small environment closure. That is the per-file process shape the earlier measurement ruled out. It also introduces a hand-written module/import parser: declared_module stops at the first nonempty line, and imported_modules recognizes only literal line-prefix syntax.

Use one batched object-store read/materialization and the existing module index/resolver, or an equivalently bounded typed route. The P0 body promised git cat-file --batch; this head contains no batch read. Do not make a partial line scanner a second authority for module/import syntax.

The same batched base population can serve the whole-base parse when environments differ. Preserve the existing separate derived-roster route: historical path membership, current deterministic renderer/current observer—not historical authored roster text.

5. Close the ambient-kernel half explicitly

This PR still leaves declaring_candidates reading the running binary's kernel_type_set. For the narrow prerequisite, either:

  • load and pass side-specific kernel_type_set values from each revision; or
  • compare the base/head blob identities for dag/std/types.dag, use the shared current value only when they are equal, and return NotEvaluated when they differ.

Do not silently claim revision-relative namespace observation while a changed kernel authority is still read from HEAD.

6. Evidence must exercise the live class

The current Rust test establishes current source -> current compiled mirror equality. It does not establish the cross-revision proposition and Rust unit tests are not the required merge-path verdict.

Before this lands, establish at least:

  • old-base func … uses + new-head fn … uses reaches namespace-wave-admission Adjudicated;
  • substituting the HEAD environment for BASE makes that same control RED/NotEvaluated;
  • environment inequality selects whole-base reconstruction and re-observes an untouched module;
  • missing, malformed, duplicate, or unsupported environment data stays NotEvaluated;
  • changed kernel-authority bytes either use a side-specific value or refuse;
  • no base-source path reaches dag_parse_environment() implicitly.

A temporary stacked execution against #10850 is acceptable evidence before landing this prerequisite; the final acceptance is #10850's required phase reaching Adjudicated after rebase.

7. Finish the carriers and the broad serializer repair

When production consumption exists, update std.syntax.ParseEnvironment's live frontier annotation—it currently says every producer is the HEAD environment. Keep base_readability_gate_refuses_a_grammar_change enrolled, append the repair receipt only when the discriminating cross-revision execution exists, and do not claim historical parser-code reproduction.

The serialize_variant_to_wire_json correction is required by this decoder but changes the general CLI wire path beyond this one value. Keep it here only with direct controls covering the changed tag-policy arms, or split that repair first; the environment equality control alone does not establish every general serializer disposition.

Finally, merge current main (this head is already diverged/behind), replace the placeholder title/body, and obtain a terminal exact-head run. The current workflow is still in progress.

Landing order after these repairs: #10970 → merge; #10850 → rebase/regenerate/review.

gunbc-ci-auto-heal and others added 10 commits September 11, 2026 05:19
The revision-selection probe refused:

  repo_relative_path_normalized: path /tmp/gunbc-parse-env-.../dag is not
  under process workspace root

The module index and entry resolver accept only paths inside the workspace
root, so a corpus materialized into the system temp directory cannot be read
by the repository's own machinery. That is not a test artifact -- the loader
would have refused every call in production too, and the previous test could
never have caught it because it only ever passed workspace_root() itself.

Materialize under target/ instead, where generated and scratch trees already
live (target/stage0-regen-candidate is the precedent). The probe still reads
the committed revision: git runs in the scratch repository's directory, and
only the extraction destination moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZZi3QsDvibJqy8k8PwzZ9
The loader (previous commits) could produce another revision's
`ParseEnvironment`, but nothing consumed it: `parse_census_fill_sources`
pinned `dag_parse_environment`, `base_records` took no environment, and the
baseline was reconstructed from untouched head records regardless of whether
the two revisions spoke the same language. This is the consumption.

AUTHORITY SIDE. `parse_census_fill_sources_with_environment` takes the
environment and threads it into both `tokenize_artifact` and
`parse_with_table_in_occurrence_scope`; the old name survives as a thin
head-grammar wrapper, which is now the ONLY place that names
`dag_parse_environment` on the census path. `parse_with_table_in_occurrence_scope`
requires its `env` for the same reason `parse_with_table_at` does: a default
would be the head's grammar, and the forgetful base caller would answer
confidently under the wrong rules.

THE PARTITION, which is the correctness core. Carrying untouched head records
assumes an untouched FILE has an untouched PARSE. That holds while both
revisions speak one grammar and fails exactly when they do not -- a keyword,
literal, operator or item-form change gives an untouched file a different
parse, so its head records are not its base records. So:

  environments identical -> changed-file reconstruction, as before
  environments differ    -> carry nothing; read every base-side file in sweep
                            scope under the base's own environment

THE CHEAP CHECK DECIDES. `environment_agreement` asks the RESOLVER for the
files declaring the environment (13 today; that number appears nowhere) and
compares git object ids. Equal ids are equal content, so agreement is
established by construction, and the ordinary pull request -- which changes no
grammar -- pays a handful of `rev-parse` calls and never materializes
anything. Only a real grammar change pays for the base corpus.

KERNEL HALF, GUARDED NARROWLY. `declaring_candidates` consults this binary's
own `kernel_type_set`, a head fact. Equal blobs for `dag/std/types.dag` mean
both revisions name the same kernel and one map serves; different blobs leave
the question open, and an open question is `NotEvaluated`. Threading distinct
base and head kernel maps is the general repair and is not this change's
subject.

AND FAILING TO ESTABLISH THE BASE ENVIRONMENT REFUSES. Not knowing which
grammar the base speaks makes every base-side declaration unreadable; falling
back to the head's would fail open on exactly the changes this exists for.

CONSUMED ADMISSIONS PAID. #11071 merged, so all three `LinuxKernelRelease
rehome` rows report CONSUMED -- the base now authors the name in
`extdeps.linux.kernel` and their delta has stopped being producible. The
obligation comes due on the roster's next touch and this change touches the
roster, exactly as those rows predicted in writing. Deleted, with the
twenty-second dissolution recorded. An empty roster admits nothing: any delta
no row names still refuses as UNADJUDICATED.

Verified: clippy --all-targets -D warnings clean; 51 wave-admission tests and
3 loader tests pass, including the probe that fails if the loader reads the
worktree instead of the revision.

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

Main parameterized `parse_census_fill_sources` on module RETENTION while this
branch parameterized it on the parse ENVIRONMENT. Taking either side would leave
two variants of one fold, which is the section 3 fork that gets consolidated
later at interest -- so the axes meet in a single core,
`parse_census_fill_sources_with(sources, retention, environment)`, and three
wrappers each vary one axis and default the other:

  parse_census_fill_sources                 head grammar, full retention
  parse_census_fill_sources_with_retention  head grammar, caller's retention
  parse_census_fill_sources_with_environment  caller's grammar, full retention

`dag_parse_environment` is named only in the wrappers and nowhere in the core, so
a base-side caller still cannot pick up head rules by omission.

The fold BODY merged cleanly and already carries both changes -- main's
`census_retain_parse_result(retention:)` and this branch's `env: environment`
threading -- so only the signature was in conflict. The base-side wrapper keeps
full retention deliberately: a baseline compared against the head must carry the
same declaration detail the head side carries, or the comparison is between
different projections.

`v1_compiler_compile.rs` is generated and was booted from main rather than
hand-merged; regen re-derives it from this authority.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZZi3QsDvibJqy8k8PwzZ9
My hand bootstrap of v1_compiler_compile.rs compiled (build_exit=0), which was
enough to build the compiler that could judge it -- and regen then reported drift
on exactly that file. The emitter's bytes are installed over mine.

The bootstrap was necessary because neither mirror alone builds the merge: main's
lacks this branch's environment axis, and this branch's lacks main's
census_heads_module_node, which main's hand-written cli_run/census_heads.rs calls.
So main's was taken (it satisfies the hand code) and only the environment axis was
hand-written onto it.

One detail that would have compiled while being wrong: there are TWO folds in that
file with the same shape. front_end_sources keeps dag_parse_environment() because
it genuinely IS the head path; only the census fold takes the parameter. A global
substitution would have built cleanly and quietly made the head path
caller-controlled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZZi3QsDvibJqy8k8PwzZ9
Both sides deleted the three #11071 rows -- main as its thirty-fifth dissolution
(#11137), this branch independently -- so main's text is taken, including its
numbering, which is the authority's count rather than my local one.

Main's #11137 row is then dissolved here, because its own trigger has fired:
"this row goes when #11137 merges", and #11137 is in origin/main with
`import extdeps.filesystem.filesystem_io { Filesystem }` at the base. The delta
it admitted has stopped being producible, so it is CONSUMED, and a consumed
row's deletion comes due on the roster's next touch. This change is that
touch. Verified rather than reasoned: the merge commit and the import line
were both read off origin/main before deleting.

The roster is empty again, which admits nothing: any delta no row names still
refuses as UNADJUDICATED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQnvBvFGNu9tNL544NJe2j
The roster was already empty on both sides -- this branch retired the #11137
admission one commit earlier, main retired it in #11165 -- so the only conflict
was the doc comment recording the retirement. Main's text is taken as the
authority's record. No code changed.

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

Three conflicts. namespace_wave_admission.rs is doc-comment only again -- main
recorded a thirty-sixth dissolution via #11121 and the roster is empty on both
sides -- so main's text is taken.

compiler_tests.rs and v1_compiler_compiler_tests_rust.rs are GENERATED. Main
changed their .dag generator (#78e8a188aad) and so did this branch (the
five-argument parse_with_table_in_occurrence_scope call); the .dag merged
cleanly, so the authority is right and only the derived files needed
re-deriving. With no build host available, both are booted from main and the
emitter's one-row delta replayed onto each -- the same move that CI's regen
phase confirmed byte-correct on #10850's parser mirror.

The generator mirror is emitted as one left-nested v1_rt::concat chain per
function, so the replay there splices the env row into the chain (one more
concat( at the head, ", E)" after the allocator row) and asserts paren balance
is unchanged. The build lane's regen phase adjudicates; a drift report means
the emitter disagreed and its bytes win.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQnvBvFGNu9tNL544NJe2j
@gunbai-bot gunbai-bot Bot changed the title fn vs func Read the base revision under its own grammar: the parse environment loader and the wave-admission partition Sep 12, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 12, 2026 23:35
Two review findings on #10970 (review 64883), both real.

1. THE PROVENANCE PROBE RACED. `the_loader_reads_the_revision_not_the_worktree`
   mutated the process cwd while the other two tests in the binary ran on other
   threads, and `workspace_root()` is a OnceLock seeded from the cwd on first
   use -- so whichever test called it first during the chdir window decided
   every test's colour. The probe this PR exists to add was nondeterministic in
   both directions.

   The loader now takes the repository explicitly at every git site --
   git_capture, blob_id_at, materialize_dag_tree_at, load_parse_environment_at,
   environment_agreement, kernel_set_serves_both -- the convention
   git_stdout(&workspace, ..) already uses in the same gate. The two production
   callers pass &workspace; the test passes the scratch repo and no longer
   touches the cwd. That also closes the production-side coupling the review
   named: the new git calls had read whatever cwd the binary was launched in
   while every other git call in run_required_wave_admission read workspace.

   The materialization DESTINATION deliberately stays under workspace_root()/
   target rather than under the repository being read: the resolver refuses
   paths outside the workspace root, which is the refusal this probe caught
   earlier. Only the git reads move.

2. Two refusal strings carried ~22-space runs mid-sentence (a triple-quoted
   insert kept its continuation indent, then rustfmt joined the lines). These
   are the messages a stopped line prints; collapsed.

ALSO PAID: the post-#10940 merge brought #10940's 181 `v2-native-route policy
split` admission rows into this roster, and the required floor on this head
reported every one as CONSUMED -- #10940 is at the base, its targets are where
the base authors them, the deltas have stopped being producible. This change
touches the roster, so the deletion is due here; thirty-seventh dissolution
recorded, roster empty.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 64883 verified and fixed in f194d9e5184.

1. The racing probe — real, and the more important half is the production side. workspace_root() is a OnceLock seeded from current_dir() on first use, so with the other two tests running concurrently, whichever called it first during the chdir window decided every test's colour. The provenance probe this PR exists to add was nondeterministic in both directions, exactly as you said.

Fixed by naming the repository explicitly at every git site — git_capture, blob_id_at, materialize_dag_tree_at, load_parse_environment_at, environment_agreement, kernel_set_serves_both — the git_stdout(&workspace, …) convention the rest of the gate already uses. The two production callers pass &workspace; the test passes the scratch repo and no longer touches the cwd at all. That closes the latent coupling you named, where the new git calls read the launch cwd while every other git call in run_required_wave_admission read workspace.

One thing kept deliberately: the materialization destination stays under workspace_root()/target rather than under the repository being read, because the resolver refuses paths outside the workspace root — that's the refusal this probe caught in an earlier round. Only the git reads move.

Verified: clippy --all-targets -D warnings clean; the loader tests run 3/3 green across three consecutive invocations (a race that passes once proves nothing); regen first_generation_equal=true.

2. The space runs — real, cosmetic, collapsed. A triple-quoted insert kept its continuation indent and rustfmt joined the lines. Agreed these matter more than cosmetic usually does: they're what a stopped line prints.

Also in this commit: the post-#10940 merge brought #10940's 181 admission rows into the roster, and CI on the previous head reported every one CONSUMED — already satisfied at the base. This PR touches the roster, so their deletion came due here — thirty-seventh dissolution, roster empty. That CI run is also the first on this branch where the floor reached a clean verdict (claims_failed=0, no thrash refusal).

— sent from cool-ibex-701

gunbc-ci-auto-heal and others added 2 commits September 13, 2026 01:59
…und the read

Two findings on #10970 (review 64939), both real, and the second found a defect.

1. ROSTER. v1_compiler.namespace_wave_admission carries an item-grain
   seed-growth roster and this change added sixteen hand-Rust declarations to
   the module without adding rows. Rostered, with the admission argument in
   `reason`: the sixteen are hand Rust for ONE reason -- git-object acquisition
   and interpreter re-entry are host boundaries the seed already crosses in
   Rust -- and they re-invent no modeled capability.

2. THE GRAMMAR-DIFFERS ARM HAD NO EXECUTED EVIDENCE. The production body is
   split behind a seam, run_wave_admission_between(workspace, base, head,
   index), and a witness (src/v1/stage0/tests/wave_admission_grammar_differs.rs)
   drives the REAL adjudication over a scratch pair whose grammars differ: a
   base admitting an extra item-form keyword with a probe declared in it, a
   head that cuts the keyword and rewrites the probe -- the measured specimen
   in miniature. RED: base_records of the base probe refuses under the head
   environment with the row's own text and reads under the base's. GREEN: the
   adjudication returns Adjudicated.

   WHAT THE RED FOUND FIRST. The loader materialized the base's whole dag/ tree
   and handed it to the module index, which parses every file it is shown under
   THIS binary's grammar -- so a base file in the base's grammar refused inside
   the loader on the way to learning the base's grammar. The class, one level
   down. The provenance probe could never see it: its diverged file was the
   environment declaration, still parseable, not a file USING the grammar.
   Repair: materialize only the environment's declaring closure.

   THE BOUNDARY THAT DRAWS, written into the row rather than left implicit: the
   loader reads a differing base so long as the base's environment closure is
   itself readable under the head's grammar. A grammar change that reaches into
   std.syntax's own closure is the compiler's bootstrap boundary and refuses as
   ClosureNotEvaluable. The specimen sits inside: none of the thirteen closure
   files at main use the cut spelling.

The failure-mode row records the climb (rung 2 on this path, witness enrolled),
what the red found, the boundary, and the residual trigger.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 64939 verified and fixed in c64521fb497.

1. Roster. Sixteen DeclarationRef rows added to namespace_wave_admission_seed_growth_justification — your count exactly. The reason now carries the admission argument rather than just the list: the sixteen are hand Rust for one reason (git-object acquisition and interpreter re-entry are host boundaries the seed already crosses in Rust), they re-invent no modeled capability (the grammar is std.syntax.ParseEnvironment, the decode is the emitter-derived serde pair, the closure is asked of the repository's own resolver), and they dissolve with the module.

2. The grammar-differs arm now has executed evidence — and the red found a defect before it went green.

The production body is split behind run_wave_admission_between(workspace, base, head, index), and tests/wave_admission_grammar_differs.rs drives the real adjudication over a scratch pair: a base whose grammar admits an extra item-form keyword with a probe declared in it, a head that cuts the keyword and rewrites the probe as fn — the measured specimen in miniature. RED: base_records of the base probe refuses under the head environment with the row's own text and reads under the base's. GREEN: the adjudication returns Adjudicated.

What the red found on its first run is the reason your finding was right in a stronger sense than "missing coverage": the loader materialized the base's whole dag/ tree and handed it to the module index, which parses every file it's shown under this binary's grammar — so a base file written in the base's grammar refused inside the loader, on the way to learning the base's grammar. The class, one level down. The provenance probe could never have seen it: its diverged file was the environment declaration (a different keyword string, still parseable), not a file using the diverged grammar.

Repair: bounded materialization — only the environment's declaring closure is shown to the index, which is the shape your earlier review asked for. That draws an honest boundary, now written into the failure-mode row rather than left implicit: the loader reads a differing base so long as the base's environment closure is itself readable under the head's grammar. A grammar change that reaches into std.syntax's own closure is the compiler's bootstrap boundary and refuses as ClosureNotEvaluable. The real specimen sits inside it — none of the thirteen closure files at main use func, which is why #10850 is adjudicable by this repair.

The row records the climb (rung 2 on this path, witness enrolled per §4b(4)), the receipt of what the red found, the boundary, and the residual trigger.

Verified on this head: build, 5,641 files parse-clean, regen first_generation_equal=true, clippy --all-targets -D warnings clean; the two witness tests and three loader tests pass serially.

— sent from cool-ibex-701

Two findings on #10970 (review 65031), both real.

1. FIXTURE COLLISION. Both tests in wave_admission_grammar_differs run on threads
   of one process, and the scratch directory was named by PID alone -- shared
   mutable state, either test able to delete or rewrite the other's repository
   mid-commit. The same class as the cwd race one review earlier: a resource
   keyed on something two threads share. The builder takes a label; one
   directory per invocation.

2. DUPLICATED HAND-SHELL. Both test files carried `sh -c "git archive | tar"` as
   a string beside a loader that already did the same through Command with no
   shell. Generalized the loader's acquisition into materialize_revision_paths
   (repo, revision, dest, paths): the closure loader calls it with its thirteen
   paths, both tests call it with ["dag"], and the shell string exists nowhere.
   One checked implementation (section 2) and no hand-shell to mark.

Verified: clippy clean; both test binaries run twice in default parallel mode
-- the mode the collision would have shown in -- all green.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 65031 verified and fixed in 08907d76760.

1. Fixture collision — real, and the same class as the cwd race one review earlier. Both tests in the binary run on threads of one process, so a directory named by PID alone was shared mutable state; either could delete or rewrite the other's repository mid-commit. The builder now takes a label (red / green), one directory per invocation.

2. Duplicated hand-shell — removed rather than marked. The loader already did git archive | tar through Command with no shell, so I generalized it: materialize_revision_paths(repo, revision, dest, paths) is now the one acquisition route. The closure loader calls it with its thirteen paths; both test files call it with ["dag"]; the sh -c string exists nowhere. That satisfies §2 (one implementation, a defect in acquisition found once) and the ctrl policy at the same time, by having no hand-shell to model or mark. Roster updated to seventeen declarations.

Verified on this head: clippy --all-targets -D warnings clean; both test binaries run twice in default parallel mode — the mode the collision would have surfaced in — all green; the roster edit parse-checks.

— sent from cool-ibex-701

…tion

Two findings on #10970 (review 65059), both real.

1. environment_agreement resolved the environment closure to decide agreement,
   then on the Differs path called a loader that resolved it again -- the same
   whole-corpus resolve twice, with the caller already holding the answer
   (section 2: carry the first value at the least common ancestor). The loader
   is split: load_parse_environment_with_closure takes the resolved set and
   environment_agreement passes the one it holds; load_parse_environment_at
   remains the resolve-once wrapper for callers that hold nothing.

2. The seed-growth roster's reason asserts the module carries NO impl block and
   that every declaration is enumerated by DeclarationRef -- the invariant an
   earlier lane paid for by converting methods to free functions, because an
   impl is the one item DeclarationRef cannot cite. This change had added
   `impl Display for EnvironmentLoadRefusal` over the top of that sentence.
   Honoured rather than amended: the text is now the rostered free function
   environment_load_refusal_text, and the sentence stays true. Roster count
   corrected to what it holds -- 5 consts, 2 types, 12 functions, 19 rows --
   with each kind named.

Verified: clippy clean; 54 + 3 + 2 tests; 5641 files parse-clean; regen
first_generation_equal=true.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 65059 verified and fixed in 50078752a8f.

1. Double closure resolve — real, fixed by carrying the value. environment_agreement resolves the closure to decide agreement, then on the Differs path handed the loader nothing and it resolved again. Split the loader: load_parse_environment_with_closure(repo, revision, &closure) takes the already-resolved set, and environment_agreement passes the one it holds. load_parse_environment_at stays as the resolve-once wrapper for callers that hold nothing. One whole-corpus resolve on the rare path, not two.

2. The impl block — real, and I'd broken a stated invariant, not just miscounted. The roster's reason says the module "carries no impl block, and every declaration in it is enumerated here", and that sentence is the invariant an earlier lane paid for by converting methods to free functions — an impl is the one item DeclarationRef structurally cannot cite. I added impl Display over the top of it. Fixed by honouring the invariant rather than amending the sentence: the text is now a rostered free function, environment_load_refusal_text, and its doc says why it is not a Display impl. The two production sites and the test sites that formatted the refusal call it; the remaining {e} sites are io::Error and String, which keep Display legitimately.

Arithmetic corrected to what the roster actually holds: 5 consts + 2 types + 12 functions = 19 rows, each kind named in the paragraph.

Verified on this head: clippy --all-targets -D warnings clean; 54 + 3 + 2 tests pass; 5,641 files parse-clean; regen first_generation_equal=true.

— sent from cool-ibex-701

gunbc-ci-auto-heal and others added 2 commits September 13, 2026 03:55
Both sides recorded the thirty-seventh dissolution of #10940's rows; main's
record is kept. Main's two #11156 rows are dissolved as the thirty-eighth:
#11156 is an ancestor of origin/main (checked by identity), their own trigger
has fired, and this change touches the roster. Their admission arguments go
with them, per the rule the thirty-seventh dissolution itself states.

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

Two findings on #10970 (review 65110), both omissions of this branch.

1. run_wave_admission_between -- the seam the grammar-differs witness drives
   and the production entry wraps -- was left off the seed-growth roster while
   the loader's declarations were added. Rostered; twenty declarations.

2. std.syntax ParseEnvironment's annotation still declared the section-3c
   frontier that #10948 wrote -- no caller reads a revision under another
   grammar, trigger is the loader landing -- while this branch IS that landing.
   The annotation now records consumption by execution: the consumer, the
   route, the enrolled witness, the boundary consumption draws, and the
   residual trigger. The stale "four-blob" closure wording is replaced with
   the measured thirteen, asked of the resolver rather than listed.

Verified: 5656 files parse-clean; regen first_generation_equal=true.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 65110 verified and fixed in 273a0743b4d. Both are omissions of mine.

1. run_wave_admission_between unrostered. I rostered the loader's declarations and left out the seam the witness actually drives. Added; the roster now enumerates twenty, with the arithmetic corrected once more and the seam named for what it is — the adjudication over an explicit repository and revision pair that the production entry wraps.

2. The ParseEnvironment frontier annotation contradicted the PR that fired its trigger. This is the one I should have caught: in #10948 I wrote that annotation specifically so the consumption claim would not live only in a PR body, named this loader as the trigger, and then landed the loader while the annotation still said no caller reads a revision under another grammar. Per §3c the status is now consumed by execution, and the annotation says so — naming the consumer, the route, the enrolled witness, and the boundary consumption draws (a differing base is readable so long as this type's own closure is readable under the head grammar; the residual trigger is the wave phase in .dag). "Four-blob" is gone: thirteen modules, asked of the resolver rather than listed. I corrected that number in conversation weeks ago and never in the source it was written into.

Verified on this head: 5,656 files parse-clean; regen first_generation_equal=true. Only .dag annotations and a roster row changed; clippy and the test binaries are unchanged from 2356940b664, where they were clean.

— sent from cool-ibex-701

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HOLD at exact head 273a0743b4dce724b1d47c6641ed4a8a4711436c. This is now the right prerequisite in architecture, but three load-bearing details still prevent LAND.

The prior HOLD’s core demands are substantially met: revision-selected acquisition is connected to production; the census fold takes an explicit environment; base_records cannot omit it; environment agreement selects changed-file versus full-base reconstruction; the kernel authority is guarded; evaluation is Hermetic with an ownership check; and the grammar-diff pair executes the real adjudication. The old scaffolding objection is closed.

1. The derived roster is parsed under the wrong revision’s grammar

The ordinary base files are historical authored text and correctly use base_environment. The derived-roster source is different: it is synthesized now, by the current renderer, from the base revision’s path membership. Yet the current route calls:

base_records(&record.rel_path, &content, base_environment.clone())

That makes current-generated source answerable to historical syntax. A future current renderer may emit a form the base grammar did not admit, producing NotEvaluated even though no historical source was unreadable. Preserve the distinction already stated in this wall: historical path membership, current deterministic renderer/current observer. Parse this synthesized source under the current dag_parse_environment() (or construct its record directly); reserve base_environment for source bytes read from the base Git tree.

2. The grammar-difference witness does not prove full-base reconstruction

In wave_admission_grammar_differs, both files that matter are changed between the commits: the environment declaration changes and the probe is rewritten from zzfunc to fn. A defective implementation that loads the base environment but still reparses only diff-touched files would pass both the RED and GREEN exactly as written.

Plant an untouched, byte-identical module whose observation differs under the two environments, and require the result that is possible only if the Differs arm rereads the complete base sweep. Equivalently, execute a mutation restoring read_set = base_parsed and require the witness to RED. The branch’s most consequential partition must not be covered by a fixture that also passes when that partition is removed.

3. The rare full-base path still performs one Git process per module

The environment closure acquisition is now bounded and correctly uses one archive. But after Differs, read_set becomes every base path and the loop still performs:

git show <base>:<rel>

once per module. On the measured corpus that is roughly 5,656 subprocesses in the exact grammar-transition run this prerequisite exists to enable. The earlier requirement was that the same bounded population acquisition serve the full-base parse. Materialize the base sweep once with materialize_revision_paths / git archive, or use git cat-file --batch, then read the files locally. Do not fix the loader’s 5,306-process discovery shape while retaining it one level later in the required consumer.

Landing state

The PR is also one commit behind current main, and GitHub currently reports the exact-head workflow as pending with zero jobs rather than an executing or terminal run. After the three repairs, merge current main and obtain a terminal exact-head required run. The acceptance signal remains the modified phase itself reaching Adjudicated/ADMITTED, not merely the isolated tests.

Once those points are closed, this is the prerequisite requested for #10850; the remaining work is then to rebase/regenerate #10850 and prove its live func→fn transition adjudicates.

gunbc-ci-auto-heal and others added 4 commits September 13, 2026 05:59
…, one acquisition

Side-chat HOLD on 273a074 (REQUEST CHANGES review 5189612133): the architecture
is accepted as the prerequisite; three details blocked landing. All three real.

1. THE DERIVED ROSTER WAS PARSED UNDER THE WRONG GRAMMAR. roster_from_path_listing
   synthesizes NEW source with the current renderer from the base tree's path
   membership; it is not bytes read from the base revision. It went through
   base_records(.., base_environment), which could refuse text no historical source
   ever carried. The base environment is now reserved for bytes actually read from
   the base tree; renderer output parses under dag_parse_environment().

2. THE WITNESS DID NOT PROVE FULL-BASE RECONSTRUCTION. Both files the fixture
   changed were diff-touched, so an implementation that loaded the base environment
   but re-parsed only the diff would have passed. For byte-identical input the
   grammar knobs are structural, so the only observation an UNTOUCHED file can
   carry across two grammars is a refusal on one side. The new fixture plants
   `fn zznm() -> Int { 3 }` before the base commit and never rewrites it; the base
   grammar makes zznm a non-name keyword, so the head reads it and the base refuses
   it. Correct answer: NotEvaluated naming that path -- the base could not read it
   under its own grammar, and only a re-read can observe that; changed-file
   reconstruction carries the head's record and admits over an unobserved
   baseline. EXECUTED THE MUTATION THE REVIEW NAMED: with read_set forced to
   base_parsed, this witness fails with "ADMITTED over a base it could not read"
   and the other two stay green -- the discriminating red, by execution.

3. THE FULL-BASE ROUTE RAN ONE git show PER MODULE -- thousands, on the rare path
   this prerequisite exists for; the process-per-file shape removed from the
   loader had survived one layer down in its consumer. The read set is now
   archived once through materialize_revision_paths and read locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPx1Ayuz3Co5ktU1uw8fti
Doc-comment-only conflict; the roster is empty on both sides. Zero of main's
doc lines are removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPx1Ayuz3Co5ktU1uw8fti
Review 65317: the seed-growth roster's structured `trigger` still read "TWO
steps" -- Class A (declaration-index carrier) and Class B (scm transport) --
while this branch added twenty declarations whose dissolution was narrated only
in `reason`. A cohort with no step on the trigger field is admitted under a
schedule that cannot retire it: section 4b(2)'s untracked stall, and section
7's "countable, prioritizable -- never a silent escape hatch".

Class C names the cohort and the capability that retires it -- the wave phase
in .dag over a ParseEnvironment VALUE -- as a capability, not an artifact (a
.dag phase still reaching a Rust loader retires nothing); states what it must
be sufficient for (the scm lane's typed repository read and modeled
interpreter re-entry); and orders it after Class B, whose transport it reads
through.

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

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in fd4bea64718 — review 65317's finding is real. I narrated the loader cohort's dissolution in reason and left the structured trigger — the field that exists to be countable — still saying two steps. The twenty declarations sat under a schedule that could not retire them; §4b(2)'s untracked stall exactly.

trigger now has three classes. Class C names the cohort and its retiring capability to the §4b(3) standard: the wave phase in .dag taking the environment as a value, stated as a capability rather than an artifact (a .dag wave phase that still reached a Rust loader would leave all twenty standing), with what it must be sufficient for (both host effects the cohort crosses becoming modeled reads — the scm lane's typed repository-read transport, and interpreter re-entry as a modeled evaluation), and its ordering (after Class B, since it reads through the transport Class B waits on).

Roster file parse-checks clean. The other approvals' checks are unchanged by this: only the trigger string moved.

— sent from cool-ibex-701

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HOLD at exact head fd4bea6471860be18900e7dedd66621aaa4b7a31 — source/content sign-off, landing freshness only.

The three findings from the prior review are closed on this head:

  1. The derived roster is current renderer output and is parsed under current dag_parse_environment(); base_environment is now reserved for bytes actually read from the base tree.
  2. The new untouched-module arm discriminates full-base reconstruction. The module is byte-identical and absent from the diff, parses under HEAD, refuses under BASE, and the production adjudication must name it NotEvaluated. The reported mutation restoring read_set = base_parsed is the right RED.
  3. The differing-grammar read set is materialized in one archive and read locally; the per-module git show loop is gone.

The loader/census wiring, Hermetic exact-owner evaluation, kernel guard, cross-grammar adjudication, permanent failure-mode receipt, and Class C dissolution trigger are all in the requested shape. I have no remaining source finding on the prerequisite.

I cannot issue LAND on the basis of the currently running workflow, because that run is explicitly bound to base 6e70be2514695710ffa34fd30502aab27437ddd6, while live main is now 4f3df2aa0bf1d7ce1a43178b381189e531197e47; this head is one main commit behind. The new main commit is substantive, and this lane has repeatedly found real composition defects after clean main merges. A terminal run against the older base therefore does not certify the tree that a direct squash merge would create now.

Required close:

  • merge current main into the branch (or obtain an equivalent composed merge-queue run against current main);
  • preserve these three repairs through integration;
  • obtain one terminal run on the resulting exact subject with required contexts successful and namespace-wave-admission reporting ADMITTED/Adjudicated.

If merging main changes the head SHA, this approval does not transfer; bring back that exact SHA. The review should then be a freshness confirmation rather than another design round.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit ab898ba Sep 13, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/cool-ibex-701-p1 branch September 13, 2026 13:15
gunbai-bot Bot pushed a commit that referenced this pull request Sep 13, 2026
The prerequisite has landed: main's wave phase now reads the base revision
under the base's own ParseEnvironment when the declaring closure's blobs differ.
This merge is the recut that lets the real func -> fn transition be adjudicated
rather than refused.

One conflict, the generated parser mirror -- #10970 threaded the environment
through parse_with_table_in_occurrence_scope, this branch carries the
table-driven parser. Mirrors booted from main (which now carries #10970's
signatures), grammar patch applied, parser delta replayed -- it applies cleanly
against #10970's mirror and zero func literals remain. CI's regen phase is the
adjudicator, as on every prior head of this branch. Zero new func declarations
on main since the last merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjkfJ59JXcYnbtgTCH2Utn
@briansrls
briansrls restored the session/cool-ibex-701-p1 branch September 13, 2026 13:17
@gunbai-bot gunbai-bot Bot mentioned this pull request Sep 13, 2026
6 tasks
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