Repository navigation
Read the base revision under its own grammar: the parse environment loader and the wave-admission partition - #10970
Conversation
`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
left a comment
There was a problem hiding this comment.
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_setvalues 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 returnNotEvaluatedwhen 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-headfn … usesreachesnamespace-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.
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
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
|
Both findings in review 64883 verified and fixed in 1. The racing probe — real, and the more important half is the production side. Fixed by naming the repository explicitly at every git site — One thing kept deliberately: the materialization destination stays under Verified: clippy 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 — sent from cool-ibex-701 |
…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
|
Both findings in review 64939 verified and fixed in 1. Roster. Sixteen 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 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 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 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 — 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
|
Both findings in review 65031 verified and fixed in 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 ( 2. Duplicated hand-shell — removed rather than marked. The loader already did Verified on this head: clippy — 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
|
Both findings in review 65059 verified and fixed in 1. Double closure resolve — real, fixed by carrying the value. 2. The impl block — real, and I'd broken a stated invariant, not just miscounted. The roster's 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 — sent from cool-ibex-701 |
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
|
Both findings in review 65110 verified and fixed in 1. 2. The Verified on this head: 5,656 files parse-clean; regen — sent from cool-ibex-701 |
briansrls
left a comment
There was a problem hiding this comment.
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.
…, 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
|
Fixed in
Roster file parse-checks clean. The other approvals' checks are unchanged by this: only the — sent from cool-ibex-701 |
briansrls
left a comment
There was a problem hiding this comment.
HOLD at exact head fd4bea6471860be18900e7dedd66621aaa4b7a31 — source/content sign-off, landing freshness only.
The three findings from the prior review are closed on this head:
- The derived roster is current renderer output and is parsed under current
dag_parse_environment();base_environmentis now reserved for bytes actually read from the base tree. - 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 restoringread_set = base_parsedis the right RED. - The differing-grammar read set is materialized in one archive and read locally; the per-module
git showloop 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
maininto 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-admissionreportingADMITTED/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.
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
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 reportsnamespace-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 — onegit archiveintotarget/, then the repository's own module index and resolver — and evaluatesdag_parse_environmentunderHermetic, selecting the declaration by exact ownership rather than a bare-name lookup a homonym could hijack. Decode isValue → 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:rev-parsecalls.Kernel guard: blob equality on
dag/std/types.dag, elseNotEvaluated—declaring_candidatesconsults this binary's head-derivedkernel_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_recordsrequires 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
dag_parse_environment()whole-value, with a non-vacuity control on the oracle.--all-targets -D warningsclean; regenfirst_generation_equal=truewith sha256s confirmed on the build host.namespace-wave-admission ADMITTEDover 5,589 modules.Incidental, found by the probe and fixed:
serialize_variant_to_wire_jsoncalled the attribute-question tag function on the data path, so every plainly-tagged coproduct failed to serialize; it now callsdata_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