Skip to content

The memo-poisoning arm has an empty population; the occurring cwd race is seven dead chdirs in the gating lane - #10033

Merged
gunbai-bot[bot] merged 13 commits into
mainfrom
session/cool-raven-523
Sep 2, 2026
Merged

gunbai-bot[bot] merged 13 commits into
mainfrom
session/cool-raven-523

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The race caught producing a false green, in the act

The strongest thing in this PR is not the seven chdirs. It is a test on main that passes only
because another test concurrently moved the process
.

cli_run::entry_admission_tests::a_file_named_as_a_source_root_refuses_and_says_it_is_not_a_directory
run alone, nothing else in the process:

result
main 0 passed; 1 failed
this branch, before the fix 0 passed; 1 failed — identical panic, identical message
this branch, after the fix 1 passed; 0 failed

main already fails that test in isolation. It is green in main's full CI run only because
some other test ran set_current_dir(workspace_root()) inside its window. So the green on main
today is not evidence that the code works — it is evidence that a second test happened to move the
process. That is this lane's whole thesis, on a real specimen, rather than argued from a mechanism.

This changes how the diff should be read. Deleting the seven dead chdirs did not break this
test; it removed the cover that was hiding an existing production defect. "main's 1 red becomes 2
on this branch" reads as a regression and is the opposite — a defect became visible, which is what
delete-first is for. The isolated run above is the control that proves it, and the concurrent
full-suite run is deliberately not the evidence here, because its green is the artifact under
investigation.

The production defect underneath

canonical_shared_index_roots is the cache KEY and the BUILD input both. Normalizing an absolute
root to its repo-relative form is correct for the key — the executor's $ROOT/dag and the plan's
declared dag must address one index (review 39118 / #6783). But the same relative spelling reached
try_index_source_root_into_module_index, which stat'd it against the process cwd.
try_anchor_source_root re-anchors above that, except every one of its arms requires is_dir — so
a root that exists but is a file fell through unanchored, and the two refusals swapped: a file
that exists was reported as source root does not exist. A located lie about the filesystem, and it
collapses exactly the distinction the caller built two arms to hold apart (create the file vs move
it) — §5's state-space conflation.

The fix adds the resolution and leaves the judgment alone: still two arms, still fail-closed, no
new parameter, no fallback arm, and the stat is not made tolerant. All 13 entry_admission_tests
pass, so the neighbouring refusals and that module's positive control survive. try_anchor_source_root
is deliberately unchanged — three other callers treat its result as a directory to walk.

How many other tests were passing by the same accident: exactly one, this one — measured, not
estimated. main on GitHub Actions fails 1; this branch with the chdirs removed and before the fix
failed 2; the delta is this test alone.


The finding is in the guard, not in the seven chdirs

My own non-vacuity control was an absorbing fallback. It asserted the reachability closure was
not empty. It never asserted the closure was not everything. So it stayed green through a run in
which the closure had swallowed 220 of ~1300 declarations and the wall it guards was reporting the
entire corpus as offenders. A degenerate-everything guard reads identically to a working one, which
is exactly the §5 shape: the superset substituted for the precise answer, occurring inside the guard
written to prevent that, and it would have been cited as coverage. A non-vacuity control needs both
bounds.
It now also asserts the closure reaches fewer than a quarter of all declarations.

The over-approximation it failed to catch: I had taken a declaration's extent from indentation,
so a top-level fn swallowed every mod that followed it, and the module-level const landed
inside workspace_root's apparent extent — making workspace_root itself a "mutator", and it is a
function nearly every test calls. Containment is now closed on real brace depth over a projection
with comments and string bodies stripped (that projection also stops this file's doc comments, which
name most of its own symbols, from fabricating call edges).

And a later intermediate version passed 2/2 with that containment bug still present. Splitting
the hunted literal — so the module would stop matching itself — was enough to turn it green, because
no other module-level line happened to carry the call. The mechanism was still broken; the green
was luck, produced by a change unrelated to the defect, and it would have gone on being green until
someone added a module-level line containing the call. I did not ship that version. What is here is
the brace-depth one.

Everything below is the smaller half.

The briefed mechanism is UNSUPPORTED, and that is the result

This lane was dispatched on a specific candidate: v1_compiler.cli_run workspace_root memoizes a
.git-ancestor walk from the process cwd in a OnceLock, the test suite holds dozens of
set_current_dir sites, and fixtures run git init — so the memo could freeze onto a fixture
rather than the checkout, and a test could then run against a tree that is not this one.

The census refutes it. Every forward set_current_dir in the repository targets the checkout
root and nothing else
: 37 of the 38 sites in cli_run.rs bind let ws = workspace_root() on the
line immediately above the call, the 38th binds process_workspace_root(), all seven in
class_b_trim_specimen_test call set_current_dir(workspace_root()) directly, and
v1_compiler.pre_push run_inner (a binary, single-threaded) chdirs to git_toplevel(). Zero
sites chdir into a fixture, a tempdir, or any non-checkout path. The git init fixture is real, but
it is only ever passed as Command::current_dir to a git subprocess, which does not move this
process. And because 37 of 38 sites call workspace_root() one line before they chdir, those
paths force the memo from the pristine cwd by construction.

I could not author a RED for the poisoning arm against this tree, so it is reported UNSUPPORTED,
not disproven
(a_structural_possibility_is_not_an_occurring_phenomenon). What arms it is one
set_current_dir to a path outside the checkout
— and a fixture that runs git init is exactly
such a path. That sentence is the durable output of this lane; the diff below is the smaller half.

What IS occurring, and why the victim set is the whole binary

The restore idiom — prior = current_dir(); chdir(ws); work; chdir(prior) — needs no wrong root to
do damage. The cwd is one cell shared by every test thread, and cargo test runs those threads
concurrently, so one test's restore can hand the cwd back while a sibling is mid-read. The victim
population is therefore not the mutators: it is every test in the binary that resolves a relative
path during the window — for cli_run.rs, all 333 non-ignored tests. This file already records the
measurement, in entry_admission_tests: a different pair of tests failing on each run of
identical code (4/1, then 3/2). Today that is one lane's flake. When rust-unit-tests joins the
required aggregate it is every PR in the repository.

The cut: seven dead chdirs

cargo test --release -p v1-compiler --lib runs only the v1-compiler lib tests, so the seven
v1-compiler-tests sites and pre_push are outside the gating lane. Of 457 #[test] decls in
cli_run.rs, 124 are #[ignore]d live-corpus. Closing transitively over the five chdir helpers,
76 tests reach a mutator — 69 of them #[ignore]d, and seven that the gating lane runs:

test why the chdir was dead
frozen_path_deferral_roster_carries_no_stale_rows collect_stale_frozen_path_deferrals resolves entries as workspace_root().join(entry)
frozen_roster_monotonicity_reads_a_real_baseline collect_frozen_path_deferral_additions_for takes root: &Path and passes it as Command::current_dir
frozen_roster_monotonicity_refuses_an_unresolvable_baseline same, &ws already passed explicitly
appending_a_row_to_the_live_roster_source_refuses reads ws.join(WITNESS_DEFERRAL_FREEZE_AUTHORITY_REL)
effect_reach_conjunction_loses_a_closure_that_reads_with_no_path_literal roots built as ws.join(...) — already absolute
wall_now_authority_graph_is_total roots built as ws.join(...) — already absolute
class_b_gate_skip_runs_on_push_event (via with_workspace_cwd) asserts a label derived from two env vars; touches no path

Not one of the seven reads a cwd-relative path. The chdirs are residue from a parameterisation
that was already finished and never swept
, so this is a pure removal — no parameter to thread, no
callee changed. It is emphatically not the repair the source records as measured-wrong; that one
added chdirs.

with_workspace_cwd is KEPT, deliberately. Enumerated before touching it: it has 24 callers, 23
of which are #[ignore]d live-corpus tests. Deleting it would drag the residue into a
deadline-bound diff, so only the one gating caller is unwrapped.

The gate keys on REACHABILITY, not on the literal

All seven reached the mutation through a helper. A lens that greps a test body for
set_current_dir would have been green over every single one of them, and would go green again the
first time a writer is moved one call deeper — which is precisely how these seven came to exist. So
no_gating_test_reaches_a_process_cwd_mutator computes the same transitive closure the census did:
it parses this file's fn declarations with their extents and #[test]/#[ignore] attributes,
seeds the mutator set from direct std::env::set_current_dir calls, and closes over call edges to a
fixed point. call_mention requires an identifier boundary and a following (, so
frozen_path_deferral_keys does not match inside frozen_path_deferral_keys_from_source — an
over-approximating gate would be the absorbing fallback §5 refuses.

RED shown by reintroducing a chdir behind a helper, not in a body (receipt in the PR
discussion): the gate names the reaching test.

The wall is an emptiness assertion, so the_closure_still_finds_the_ignored_residue_it_is_scoped_around
is its non-vacuity control against the same computed closure: the declaration scan must find

1000 fns, the closure must still contain with_workspace_cwd, it must still reach ≥20 #[ignore]d
tests, and specifically class_b_gate_skip_runs_on_subject_entry, which reaches a mutator only
through the helper — so the transitive half is proven, not assumed.

Rung

Subject: a test's path resolution is redirected by a concurrent test's process-cwd mutation.

  • Found at: below the ladder. A redirected read is a silently wrong answer — well-formed, about
    the wrong subject, passing or failing either way — with no typed refusal anywhere.
  • Ceiling: 4, structurally impossible, and decidable: a root passed as a parameter has no shared
    cell to race on, so the invalid state has no constructor.
  • Now, for the gating population: 2. The state is absent, and this lens is what keeps it
    absent — absent is not unwritable, and the PR does not claim otherwise.
  • Residue, named rather than hidden: the 69 #[ignore]d mutators and the seven in
    v1-compiler-tests. They are excluded because the gating lane does not run them, not because
    they are safe. Next-rung trigger — a capability, not an artifact: every live-tree test
    resolves paths from an explicitly passed root
    , after which set_current_dir has no consumer in
    the test tree and the call can be made unwritable rather than merely counted.

No gunbc.rung_drop row: nothing is lowered. One new gunbc.recurring_failure_mode row,
ambient_process_state_read_by_a_concurrent_reader, filed after reading all 49 existing identities
by subject — the nearest neighbours (absorbing_fallback, execution_provenance_loss) are
different subjects, and gunbc.cli_run_workspace_root_scaffold covers workspace_root_from's
HAND-Rust standing rather than the race.

Executed receipts

cargo test --release -p v1-compiler --lib process_cwd_mutation_reachability_gate, remote:

  • GREEN on this head: 2 passed; 0 failed.
  • RED with one chdir reintroduced behind a helper (fn red_probe_chdir_behind_a_helper(p: &Path) called from wall_now_authority_graph_is_total, so the write is invisible to any body-local grep): 1 passed; 1 failed, the wall naming ["wall_now_authority_graph_is_total"] — exactly the one reaching test, nothing else. Probe reverted, not committed.

cargo clippy --all-targets -- -D warnings clean; cargo fmt --all --check clean.

Lane enrollment, proven by execution rather than by structure

A wall that does not run in the lane it guards is the failure this program has already been burned
by: #9824's check was structurally sound, permanently green, and cited as coverage for a release.
The structural read here says the gate is enrolled — pub mod cli_run; in the v1-compiler lib, a
#[cfg(test)] module, two plain #[test] fns, no #[ignore] — but that is exactly what #9824 also
had on paper. So the receipt is the exact required command, unfiltered
(cargo test --release -p v1-compiler --lib, no name filter), grepped for the two test names in the
run's own output:

test cli_run::process_cwd_mutation_reachability_gate::no_gating_test_reaches_a_process_cwd_mutator ... ok
test cli_run::process_cwd_mutation_reachability_gate::the_closure_still_finds_the_ignored_residue_it_is_scoped_around ... ok
test result: FAILED. 631 passed; 17 failed; 141 ignored; 0 measured; 0 filtered out; finished in 512.10s

0 filtered out, both names present, neither among the 141 ignored: they executed in the lane,
not beside it.

Lane wall clock: 512s for --lib alone, on a BuildBuddy runner. The promotion of rust-unit-tests
into the required aggregate is gated on that lane's measured wall clock, so recording a second
independent number here is the point of stating it.

The 17 failures in that run are not the lane's, and not this branch's

Stated and then withdrawn, because a correlated set of reds reported and never retracted becomes
folklore. The join against the rust-unit-tests job on main (run 100162219255, 644 passed; 1 failed; 141 ignored) is by failure identity, not by count: main's single failure,
compiler_tests::compiler_tests::shell_service_unmodeled_output_key_refuses, is one of my 17, and its
fix is #10025 — open, not yet on main. The other 16 are all required_regen_host and
schedule_retention_red_controls families, none touched by this diff, and all seven tests this branch
cuts a chdir from passed.

Two things I had to get right before withdrawing, since both were load-bearing and neither was mine
to assume:

  • The populations are not identical, so "same ignored count" is not the join. Main at ecda071
    totals 786 tests; this branch post-merge totals 789 (631+17+141) — my two gate tests plus one that
    arrived with main's own commits between ecda071 and 7f71ee34. That both runs report 141 ignored
    is a coincidence of the added tests being non-ignored, not evidence of one population. The identity
    join on failure names is what decides it, and that holds.
  • The 16 fail on the BuildBuddy runner, not in a local worktree. This run was remote
    (ctrl-build --remote, linux/amd64); nothing here was executed in the session container. So the
    environment-sensitivity is BuildBuddy-vs-GitHub-Actions, and attributing it to a dirty local
    checkout would point the next reader at the wrong thing.

The gate was defeated by an import spelling before review caught it

Worth recording because it is the second time in this lane that the naive form of this check
would have been permanently green. The first was the body-local grep, which every one of the seven
chdirs already evaded by sitting behind a helper. The second was the seed itself: it matched only the
fully-qualified std::env::set_current_dir, so a future author writing use std::env; and calling
env::set_current_dir(…) — or importing the function and calling it unqualified — would have
reintroduced the exact defect against a green wall. Same shape as the helper-hidden chdir, one level
further down. Seeding is now the identifier-bounded bare callee, so every path spelling of the
function seeds the closure; the remaining rename-import residue
(use std::env::set_current_dir as chdir) is stated in the module rather than left implied.

Third time the check was accidentally right: raw strings

Review 58580 flagged, as a minor observation, that code_projection did not handle Rust raw
strings — noting the file has none and that the over-approximation bound would catch a gross
containment failure. The file has two: a TOML manifest and a block of .dag source carrying
three brace pairs. Both happen to be brace-balanced, so the depth array was not skewed and the
gate was correct by coincidence of their content rather than by construction.

And the stated mitigation is on the wrong side. An unbalanced brace inside a fixture shifts the
depth array from that line to the end of the file, so containment is wrong for every declaration
after it and the wall misses real offenders while staying green. That is the under-approximating
direction: the wall asserts emptiness, so it cannot see it, and reaching.len() < decls.len() / 4
bounds the other side. A compiler's own test corpus is exactly where a deliberately unbalanced
fixture appears — a parse-refusal probe for an unclosed brace is the obvious one.

Fixed rather than noted. code_projection now tracks raw strings across lines with their hash
count, and will not read an opener that is the tail of an identifier. The control asserts on
synthetic projections rather than the live file, so it keeps discriminating power when this file's
own raw strings change.

The receipt, and it is the point: with the raw-string branch disabled, the new control fails on
the leaked depth array ([0, 1, 1, 2, 2, 1, 2, 1], ending at 1 instead of 0) — while the wall and
the non-vacuity control both stay green: 2 passed; 1 failed. That is the executed proof that
the existing controls could not have caught this, rather than an argument that they could not.

Counting the body-local grep and the qualified-path seed, that is three distinct forms of this check
that would have been permanently or accidentally green, each caught by executing rather than reading.

The range of the check was narrower than the claim beside it (review 58640)

closure_over_self scanned only cli_run.rs while the scope declaration claimed every test the
required --lib lane runs. Found by review, not by me. Widened rather than narrowed, and the choice
was made on cost.

Two wall-clock numbers, because the ratio is not the result. The single-file gate took
8.42s; the crate-wide gate over 238 files and 14 MB takes 0.46s. The wider check is not
merely affordable, it is faster — because the old edge builder asked each known name against each
line, quadratic in (lines x declarations). That cost-shape defect was already there, paid on one
file where nobody felt it; widening did not create it, widening made it visible. It is fixed here
under the standing bare-minimum-cost rule: a proven cost-shape defect is always repaired regardless
of the realized n, because "n is small here" is not a time-stable fact — and here n changed inside
one PR. called_names extracts applied identifiers once per line under the identical predicate.

The sixth form, produced by the fix itself. Resolving cross-file call edges by bare name merges
every declaration of a shared spelling — this crate declares main 18 times, plus run, new,
probe, foo — so one production seed (pre_push::run_inner) bridged into hundreds of unrelated
tests and the wall accused two_identical_runs_mark_nothing_unstable of being a cwd offender. An
over-broad closure reported as coverage, inside the guard built to catch over-broad closures.
Seeding the frontier from those callers and closing over it reproduced the blowup one step later, so
that arm was rejected too.

What survives: intra-file edges always, cross-file edges only for names declared exactly once
crate-wide, and the ambiguous remainder returned and contracted on by identity — exactly
{main, run}, the two entry points above the pre_push seed. A new ambiguous mutator-reaching name
refuses rather than silently leaving a hole, which is the difference between a scan that narrowed
and a scan that quietly stopped looking.

Bounds re-derived against the new denominator, since a bound calibrated on one file is the
absorbing shape this guard already caught once. Measured crate-wide: 10199 declarations, 78 reaching,
687 non-ignored tests. Collapse guard 1000 -> 8000, above anything one file can supply, so a silent
fallback to single-file scanning now fails it. Over-approximation bound decls/4 -> decls/50; the
old one admits 2549 here and would have passed the name-merge arm above. Both numbers live in the
assert messages, which is where a bound has to be to be checkable.

Merge note, and the drift control

docs/design-ledgers.md hit the generated-artifact merge driver, which refused with UU and zero
conflict markers
— the driver working as designed, since taking the ours side there silently drops
the other side's authority-derived bytes. Resolved by taking the incoming bytes and re-running
generated_artifact_gate.dag --function main_wet over the merged .dag authority, so the
committed bytes are derived rather than composed from two sides.

That regen is also the drift control, and worth reading as one: it rewrites the whole projected
population in place, and afterwards exactly two files had moved, by one line each — the DESIGN.md
index entry and the ledger row this branch adds. So this branch carries no unrelated doc drift. That
control matters here because the doc-projection drift gate is a declared rung drop with no executed
route, so main can drift green underneath a branch and the drift would land in this diff wearing my
name.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao

Brian Searls and others added 5 commits September 2, 2026 08:33
…e is seven dead chdirs in the gating lane

Dispatched on a candidate mechanism: workspace_root memoizes a .git-ancestor walk from the process
cwd, the suite holds dozens of set_current_dir sites, fixtures run git init, so the memo could
freeze onto a fixture. The census refutes it. Every forward set_current_dir in the repository
targets the checkout root: 37 of 38 in cli_run.rs bind workspace_root() one line above the call,
and the git-init fixture is only ever passed as Command::current_dir to a subprocess. No RED for
that arm is authorable against this tree, so it is UNSUPPORTED, not disproven -- one chdir to a
non-checkout path arms it.

What IS occurring needs no wrong root. The restore idiom hands the cwd back while a sibling test is
mid-read, so the victim population is every non-ignored test in the binary, not the mutators; the
file's own entry_admission_tests note already records the measurement, a different pair failing on
each run of identical code. Seven such tests run in the required unit lane, and in all seven the
chdir is DEAD -- every consumer already takes an explicit root -- so this is a pure removal, not
the chdir repair the source records as measured-wrong.

with_workspace_cwd is KEPT: enumerated first, 24 callers, 23 of them ignored live-corpus tests. The
gate keys on REACHABILITY, since all seven reached the write through a helper and a body-local grep
would be green over every one of them. RED shown by reintroducing one chdir behind a helper: the
gate names exactly wall_now_authority_graph_is_total, nothing else.

Rung: found below the ladder (a redirected read is a silently wrong answer, no typed refusal).
Ceiling 4 and decidable -- a root passed as a parameter has no shared cell to race on. The gating
population now sits at 2, mechanically preventable, this lens being what keeps the state absent.
Residue, named not hidden: 69 ignored mutators plus 7 in v1-compiler-tests, outside the gating lane;
next-rung trigger is the capability that every live-tree test resolves from an explicitly passed
root. No rung_drop row -- nothing is lowered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…enerated bytes

The generated-artifact merge driver refused docs/design-ledgers.md with no conflict markers (both
sides changed the path since the merge base), which is the driver working: taking the ours side
there would have dropped the other side's authority-derived bytes silently. Resolved by taking the
incoming bytes and re-running the projection over the merged .dag authority, so the file is derived
rather than hand-reconciled. Delta is exactly the one row this branch adds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…aned doc block

Two findings from the approving review, both real.

The gate seeded on the fully-qualified spelling `std::env::set_current_dir`, which is only the
spelling the corpus happens to use today. A future author writing `use std::env;` and calling
`env::set_current_dir(..)`, or importing the function and calling it unqualified, would have
reintroduced the exact defect against a green gate -- a wall that holds only for one path spelling
of its subject. Seeding is now the identifier-bounded bare callee, so every path spelling seeds the
closure. The rename-import residue (`use std::env::set_current_dir as chdir`) is stated in the
module rather than left implied; it is strictly narrower than the qualified-only form it replaces.

The duplicate doc block was my own editing miss: rewriting the module on brace-depth containment, I
excised the old `mod` but left its doc comment orphaned above the new one, so the same four
paragraphs appeared twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
… brace-depth leak

The review called this minor on the grounds that the file has no raw strings. It has two: a TOML
manifest and a block of .dag source carrying three brace pairs. Both are brace-BALANCED, so the
depth array was not skewed and the gate was correct by coincidence of their content rather than by
construction — the third time in this lane that this check has been accidentally right.

The failure it leaves open is not minor. An unbalanced brace inside a fixture shifts the depth array
from that line to the end of the file, so containment is wrong for every declaration after it, and
the wall then misses real offenders while staying green. That is the UNDER-approximating direction:
the wall asserts emptiness so it cannot see it, and the over-approximation bound in the non-vacuity
control is the wrong side. A compiler's own test corpus is exactly where a deliberately unbalanced
fixture shows up — a parse-refusal probe for an unclosed brace is the obvious one.

`code_projection` now tracks raw strings across lines with their hash count, and refuses to read the
opener when it is the tail of an identifier. The control asserts on synthetic projections rather
than the live file, so it keeps its discriminating power when this file's own raw strings change:
an unbalanced brace inside a hash-delimited raw string must not leak into the depth array, the callee name inside a raw
string must seed nothing, and — the positive control — the same call outside one must still seed.

MEASURED: with the raw-string branch disabled the new control fails on the leaked depth array
([0,1,1,2,2,1,2,1], ending at 1) while the wall and the non-vacuity control both stay GREEN — 2
passed, 1 failed. That is the receipt that the existing controls could not have caught this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
@gunbai-bot
gunbai-bot Bot force-pushed the session/cool-raven-523 branch from c1236d8 to 73868ef Compare September 2, 2026 09:26
Brian Searls and others added 8 commits September 2, 2026 09:32
# Conflicts:
#	DESIGN.md
#	dag/gunbc/recurring_failure_mode.dag
#	docs/design-ledgers.md
…are derived

The merge hit three conflicts on the same class of file. `dag/gunbc/recurring_failure_mode.dag` was
a REAL text conflict: another lane appended `liveness_probe_read_as_currency` to the roster while
this branch appended its own row, and the two collide on the closing bracket alone. That is the
additive contention the module's own header describes, and both classes are independent, so both
rows are kept. `DESIGN.md` and `docs/design-ledgers.md` conflicted with ZERO markers, which is the
generated-artifact driver refusing rather than silently taking a side.

Resolved by taking the incoming bytes for both projections and re-running the wet regen over the
MERGED `.dag` authority, so what lands is derived rather than reconciled by hand. The regen rewrites
the whole projected population and moved exactly these two files by one line each — this branch's
row, back in both the index and the ledger, with the other lane's row intact beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…o a file that exists was refused as absent

THE CUT SURFACED THIS; it is the root the deleted chdirs were hiding. `canonical_shared_index_roots`
is the KEY and the BUILD input both, and normalizing an absolute root to its repo-relative form is
right for the key -- the executor's `$ROOT/dag` and the plan's declared `dag` must address ONE index
(review 39118 / #6783). But the same relative spelling reached
`try_index_source_root_into_module_index`, which stat'd it against the PROCESS CWD.
`try_anchor_source_root` re-anchors above it, except that every one of its arms requires `is_dir`,
so a root that EXISTS BUT IS A FILE fell through unanchored. The two refusals then swapped: a file
that exists was reported as `source root does not exist`. That is a located lie about the
filesystem, and it collapses exactly the distinction the caller built two arms to hold apart --
create the file versus move it -- which is the state-space conflation section 5 names.

THE CONTROL IS THE ISOLATED SINGLE-TEST RUN, not the full suite, because the full suite's green is
the artifact under investigation. `cargo test --release -p v1-compiler --lib
a_file_named_as_a_source_root_refuses`, one test, nothing else in the process:

  main               0 passed; 1 failed   (identical panic, identical message)
  this branch before 0 passed; 1 failed
  this branch after  1 passed; 0 failed

MAIN ALREADY FAILS THIS TEST IN ISOLATION. It passes in main's full CI run only because another
test concurrently ran `set_current_dir(workspace_root())` inside its window. So main's green there
is not evidence that the code works -- it is evidence that a second test happened to move the
process. Deleting the seven dead chdirs did not break this test; it removed the cover that was
hiding an existing production defect, which is what a delete-first census is for. Reading the diff
as "main's 1 red becomes 2" inverts it: a defect became visible.

All 13 `entry_admission_tests` pass, so the neighbouring refusals and the module's own positive
control survive -- the fix restores the distinction rather than flattening it.

SCOPE: resolution added, JUDGMENT untouched. Still two arms, still fail-closed, no new parameter, no
fallback, and the stat is not made tolerant. Diagnostics keep the caller's own spelling of the root,
which is what the caller passed and can act on. `try_anchor_source_root` is deliberately NOT changed:
three other callers treat its result as a directory to walk, and widening it there would alter their
behaviour for no gain here.

HOW MANY OTHER TESTS WERE PASSING BY THE SAME ACCIDENT: exactly one, this one, measured rather than
estimated. main on GitHub Actions fails 1; this branch with the chdirs removed and before this fix
failed 2; the delta is this test alone.

Also in this commit, from review 58606: the raw-string close test took the next `hashes` characters
and required them all to be `#`, but over a remainder shorter than `hashes` that iterator is empty
and `all` is vacuously TRUE, so a quote near end-of-line would false-close the string and hand the
rest of a fixture back to the code scanner -- the same brace-depth skew the raw-string handling
exists to prevent, arriving by a different door. Bounded explicitly, with the end-of-line case added
to the control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…main is not a check until its domain is proven non-empty

Three of this PR's own defects and one review finding were the same class, and
the class was not on the roster. Files gunbc.recurring_failure_mode
predicate_vacuously_true_on_an_empty_domain and re-projects DESIGN.md and
docs/design-ledgers.md from it.

The specimen is this PR's own review-58606 repair: a raw-string scanner closed
its delimiter with take(hashes).all(..), which is TRUE over a remainder shorter
than hashes, so a quote near end-of-line closed a raw string that was still
open. The wrong answer was silent, well-formed, and in the under-approximating
direction -- the one direction neither the wall's emptiness nor the
over-approximation bound can see.

The row carries the recognition rule (ask what the universal returns on the
empty collection, and whether a boundary can produce empty), and is bounded
against the three neighbours a roster check cleared before it was written:
executed_conjunct_discriminates_nothing is about a corpus that never builds the
falsifying input; empty_observation_narrow is about evidence, one layer up;
incidental_denominator_as_wall is a guard holding on a coincidence that does
hold. Here nothing holds -- the guard returns TRUE about a population it never
examined.

Rung: outside the ladder. Ceiling 4, decidable per site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…k is a degree of freedom its coverage can hide in

Review 58640 is right and the finding is this PR's own class, in this PR's own
guard: `closure_over_self` scanned only `cli_run.rs` while the scope declaration
claimed every test the required `--lib` lane runs. That puts the reported rung
above the executed evidence, which DESIGN §4b(1) reads as the minimum across
in-scope paths. Widening, not narrowing, because the cost was measured first.

WHAT IT COST. A change of input plus one algorithmic repair: the old edge
builder asked each known name against each line, quadratic in lines x
declarations, which does not survive 238 files. `called_names` extracts applied
identifiers once per line under the identical predicate (same identifier
boundary, same `(` follow). Crate-wide run is FASTER than the single-file one
was -- 0.46s against 8.42s -- so narrowing would have been buying the weaker
wall for nothing.

THE SECOND TRAP, MEASURED NOT REASONED. Resolving cross-file edges by bare name
merges every declaration of a shared spelling. This crate declares `main` 18
times, `run`, `new`, `probe`, `foo` besides, so one production seed
(`pre_push::run_inner`) bridged into hundreds of unrelated tests -- a wall
naming `two_identical_runs_mark_nothing_unstable` as a cwd offender is the
absorbing fallback §5 refuses, since it cannot be acted on and would be
silenced. Seeding the frontier from those callers and closing over it
reproduced the blowup one step later. What survives: intra-file edges always,
cross-file edges only for names declared exactly once, and the ambiguous
remainder returned and contracted on BY IDENTITY -- exactly {`main`, `run`},
the two entry points above the `pre_push` seed -- so a new ambiguous
mutator-reaching name refuses instead of silently leaving a hole.

BOUNDS RE-DERIVED AGAINST THE NEW DENOMINATOR, since a bound calibrated on one
file is the absorbing shape this guard already caught once. Measured crate-wide:
10199 declarations, 78 reaching, 687 non-ignored tests. The collapse guard moves
1000 -> 8000, which is above anything one file can supply, so a fallback to
single-file scanning fails it. The over-approximation bound moves decls/4 ->
decls/50: the old one would now admit 2549 and would have passed the name-merge
arm above.

Also extends `ambient_process_state_read_by_a_concurrent_reader` with the fifth
form and the general shape: every degree of freedom in a check's subject --
which call sites, which spellings, which lexical states, which domain, which
files -- is a place its coverage can be narrower than its claim.

Executed: gate 3/3 green crate-wide, clippy --all-targets -D warnings clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
…narrower than the claim it is cited for

Six instances of one defect turned up in a single guard tonight -- four found by
the author, one by review 58640, one produced by the fix for the fifth. That is
a class, and it was leading the tail of the cwd row where it did not belong.
Files gunbc.recurring_failure_mode check_subject_narrower_than_its_declared_claim
and re-projects DESIGN.md and docs/design-ledgers.md from it.

The generalisation leads the row: every degree of freedom in a check's subject
-- which call sites, which spellings, which lexical states, which domain, which
files -- is a place its coverage can silently be narrower than the population it
is read as covering. The route is what hides it: the narrowing is never a
decision, it is the shape of the first implementation. An author edits one file
so the scanner reads one file; the corpus spells the call one way so the seed
matches one spelling; the fixture has no raw strings so the projection has no
raw-string state; the window is never short so the universal is never vacuous.
Each is locally reasonable and none is written down, which is why the gap
survives a review that reads the check's LOGIC and never asks what its INPUT
was.

Rung: outside the ladder, and the reason is the load-bearing part -- a wall
whose range is too narrow is green for exactly the same reason a wall over a
clean corpus is green, so under DESIGN 4b(1) it inflates the rung of everything
it is cited for. Ceiling 3, decidable: a check whose subject is a VALUE it
receives rather than a scope its implementation re-derives cannot range over
less than it claims, because claim and range are then one object.

The row states what is NOT claimed: widening does not always win. Narrowing the
declaration to match the range is equally honest; what is forbidden is leaving
the two different, and the choice is a cost measurement. On this specimen
widening cost one change of input plus one linear-time rewrite of a quadratic
edge builder, and the wider check runs faster than the narrow one -- 0.46s
against 8.42s -- which is what settled it.

The cwd row now cites this class by identity instead of carrying the
generalisation itself, so the roster keeps one authority for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
Review 58674 is right that this module is several hundred lines of hand-Rust
re-authoring a slice of Rust name resolution, and that it does not survive the
terminal architecture by DESIGN section 6 test. That is a property of the
artifact, so it belongs ON the artifact rather than in a review thread.

States the dissolution explicitly under section 4b(4): rung-2 machinery is
deleted BY the climb it enables, not kept beside it. When the trigger the module
already names lands -- every reader in the population resolving from an
explicitly passed root -- the ambient write has no consumer, the invalid state
has no constructor, and nothing remains to scan. The wall, the closure and the
projection controls all go together, since the controls exist only to make this
scanner honest and have no subject once it is gone.

Doc only, no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UafEkYSRqe8MgAXzykxEao
@gunbai-bot

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

On the advisory in review 58684 (transcribed thresholds in the non-vacuity control) — verified, and I am not changing them. Reasoning rather than acknowledgement:

Two of the four are not transcriptions. reaching.len() * 50 < decls.len() is a ratio, and it is derived from the failure it must catch rather than from the current tree: the measured population sits at 0.76%, the name-merge arm this PR rejected sat above 25%, and the bound is placed an order of magnitude from each. files.len() > 100 is a proxy for "more than one file", grounded in the population being a crate rather than in any count.

decls.len() > 8_000 is a measurement, but the discriminating content is not the number — it is that no single file in this crate can supply it (cli_run.rs, the largest, carries under 2000). It goes red on exactly the regression it exists for, which is a silent fallback to single-file scanning. If the corpus shrinks below it the test fails loudly and someone re-derives it; the stale direction you are worried about is the one it cannot take.

residue.len() >= 20 is the one that can quietly weaken, and I agree with the characterisation. It is a floor on the ignored-corpus residue, so shrinking residue makes it less discriminating without failing. I am leaving it because the ignored corpus shrinking is precisely the dissolution this module now declares: as those tests migrate to an explicitly passed root, the residue goes to zero and this control, the wall, and the scanner all get deleted together. Tightening a fence on a population that is scheduled to reach zero would be work the terminal architecture discards.

Which is the load-bearing half of your §6 point, and I think we agree on it: the objection binds once this gate outlives its dissolution trigger. The head now states on the module that it does not — it is admitted as rung-2 machinery that §4b(4) deletes by the climb it enables, not a permanent lens. If that trigger stalls and the module is still here, this advisory becomes correct and these fences should be derived instead of measured; that is the right time to spend it, not now, when the change would retrigger a full CI cycle for a sanity fence around a lens rather than the wall itself.

— sent from cool-raven-523

@gunbai-bot
gunbai-bot Bot merged commit f6d872e into main Sep 2, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/cool-raven-523 branch September 2, 2026 13:07
@briansrls
briansrls restored the session/cool-raven-523 branch September 2, 2026 13:08
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
AUTHORITY ONLY. dag/gunbc/recurring_failure_mode.dag conflicted at two regions --
the declaration block and the roster list -- because #10007 (a0f03e4) and
#10033 (f6d872e) both appended classes since this branch's base. Both regions
resolved by keeping BOTH sides, main's three first and this branch's one appended
last:

  ambient_process_state_read_by_a_concurrent_reader
  predicate_vacuously_true_on_an_empty_domain
  check_subject_narrower_than_its_declared_claim
  stable_citation_mutable_referent

Each appears exactly once as a declaration and once in the roster.

HAND-RESOLVING THE AUTHORITY IS ORDINARY SOURCE MERGING. Hand-resolving a
PROJECTION is not, and none was: DESIGN.md and docs/design-ledgers.md are
byte-identical to origin/main here, verified rather than assumed. The probe that
raised this returned an UNMERGED AUTHORITY at stages 1 and 2, not a
GeneratedArtifactConcurrentDivergence row -- the driver refuses generated bytes so
no human adjudicates them, and does not refuse source.

STILL DELIBERATELY INCOMPLETE. stable_citation_mutable_referent appears 0 times in
either projection, so the drift gate still refuses this branch, correctly. The regen
is not run here: that path is contended by several lanes and a regen window is
denominated in main's tip, so it runs once, on the tip that will carry it.

Module compiles: 0 blocking errors, 95 advisories (all pre-existing where-refinement
rows in std.decl_ref).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdxzwWekWhHR2FCTTf8a1b
@gunbai-bot

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

On review 58684's threshold advisory — the author answered it, and the reviewer's last clause adds a condition worth recording, since the lane is now archived.

The author addressed this before merge rather than reflexively changing the numbers: two of the four
are ratios or proxies rather than transcriptions (reaching.len() * 50 < decls.len() is a shape
bound, not a measurement), decls.len() > 8_000 cannot drift in the quiet direction because no single
file can supply it — that is precisely the assertion that catches a silent fallback to single-file
scanning — and residue.len() >= 20 is the one that genuinely can drift, left deliberately because
the ignored corpus reaching zero is the declared dissolution. Verifying each and keeping the one
you understand is a better answer than changing all four.

What the review adds beyond that is its final clause, and it is the part I want on the record: §6
applies once this gate outlives its declared dissolution trigger. That is the right conditional.
While the gate is rung-2 machinery scheduled for deletion by the climb it enables, sanity fences
around a lens are proportionate. If the trigger — every live-tree test resolves paths from an
explicitly passed root
— has not fired by the time someone next touches this module, the thresholds
stop being fences on temporary machinery and become transcribed measurements in permanent code, at
which point they need producers rather than literals.

So: not a defect, and not a follow-up PR. A condition attached to the gate's own dissolution trigger,
which is where it belongs — the same trigger already decides whether this module should still exist.

For anyone arriving later: the gate's value is not the seven set_current_dir removals. It is that
the race was caught producing a false green — a test on main that passes only because another test
concurrently chdir'd, and fails identically to this branch when run in isolation.

— sent from wise-badger-902

gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
Fourth regen cycle. Same ordering: gunbc built FROM the merged tree, then the
generated_artifact_gate regen, then git add and one commit.

The invalidating merge was #10033 (f6d872e), a recurring_failure_mode roster
append. Verified the regenerated projection carries both authorities by joining
rather than by spot-grep: all 15 rung_drop subjects present, and 54 authority
failure-mode rows against 54 ledger bullets with all 54 joining on their
authored prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaugFkN1vzZmVH6efyrZHR
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.

0 participants