Skip to content

Main is red: #9886's stale mirror won the merge over #9938's emitted control - #10017

Merged
briansrls merged 1 commit into
mainfrom
fix/main-stage0-drift
Sep 2, 2026
Merged

briansrls merged 1 commit into
mainfrom
fix/main-stage0-drift

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

required-witnesses-build fails on 4059156 and on bb96afa with
regen FAIL generated surface drift: compiler_tests.rs, std_realization_schedule.rs. Reproduced locally at rc=1 on the same two
files before anything was installed, so the repair is evidenced rather
than assumed.

ROOT CAUSE, and it is not "the branch predated the control". git log -S
on the emitted fn
function_value_adapter_fires_exactly_where_the_impl_fn_bound_is_emitted
in the mirror has exactly two touches: 4e03636 (#9938) ADDS the
authority row and the emitted fn, and 4059156 (#9886) REMOVES the fn
while leaving the roster row standing. 4e03636 is an ANCESTOR of
#9886's first parent. So #9886 regenerated its mirror against an earlier
state, then merged main — which by then carried #9938's authority — and
its own stale projection bytes won.

That is the hazard DESIGN.md already records for the generated-artifact
driver: taking the ours side drops the other side's authority-derived
bytes with no conflict. What is new is the ROUTE. This landed through
GitHub, which does not run the merge driver at all; the driver would have
refused GeneratedArtifactConcurrentDivergence. Not a new class — the
existing one arriving through the one path where the wall is absent.

Why it matters beyond the red: main was carrying an enrolled §4b(4)
evidence control that exists in the authority and does not exist in the
artifact that executes. The roster still cited it as coverage. A control
that silently stops existing is worse than one that fails.

std_realization_schedule.rs is the same shape at one line: committed
population_index: Nat against the emitted i64.

Repair is the regenerated bytes, nothing hand-edited. Verified by
execution, not by inspection: claim_executor was REBUILT against the
installed mirror and the full regen re-run, giving
first_generation_equal=true planned=150 executed=150 adjudicated=150,
rc=0. A regen that greened only because it compares against the file I
edited would prove nothing, which is why the rebuild is the load-bearing
step.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01L9g69G7ZkiCXGCeUUJgo9D

🤖 Generated with Claude Code

https://claude.ai/code/session_01L9g69G7ZkiCXGCeUUJgo9D

…control

`required-witnesses-build` fails on 4059156 and on bb96afa with
`regen FAIL generated surface drift: compiler_tests.rs,
std_realization_schedule.rs`. Reproduced locally at rc=1 on the same two
files before anything was installed, so the repair is evidenced rather
than assumed.

ROOT CAUSE, and it is not "the branch predated the control". `git log -S`
on the emitted fn
`function_value_adapter_fires_exactly_where_the_impl_fn_bound_is_emitted`
in the mirror has exactly two touches: 4e03636 (#9938) ADDS the
authority row and the emitted fn, and 4059156 (#9886) REMOVES the fn
while leaving the roster row standing. 4e03636 is an ANCESTOR of
#9886's first parent. So #9886 regenerated its mirror against an earlier
state, then merged main — which by then carried #9938's authority — and
its own stale projection bytes won.

That is the hazard DESIGN.md already records for the generated-artifact
driver: taking the ours side drops the other side's authority-derived
bytes with no conflict. What is new is the ROUTE. This landed through
GitHub, which does not run the merge driver at all; the driver would have
refused GeneratedArtifactConcurrentDivergence. Not a new class — the
existing one arriving through the one path where the wall is absent.

Why it matters beyond the red: main was carrying an enrolled §4b(4)
evidence control that exists in the authority and does not exist in the
artifact that executes. The roster still cited it as coverage. A control
that silently stops existing is worse than one that fails.

`std_realization_schedule.rs` is the same shape at one line: committed
`population_index: Nat` against the emitted `i64`.

Repair is the regenerated bytes, nothing hand-edited. Verified by
execution, not by inspection: claim_executor was REBUILT against the
installed mirror and the full regen re-run, giving
`first_generation_equal=true planned=150 executed=150 adjudicated=150`,
rc=0. A regen that greened only because it compares against the file I
edited would prove nothing, which is why the rebuild is the load-bearing
step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9g69G7ZkiCXGCeUUJgo9D
@briansrls
briansrls merged commit 52aac48 into main Sep 2, 2026
2 of 5 checks passed
@briansrls
briansrls deleted the fix/main-stage0-drift branch September 2, 2026 06:56
briansrls pushed a commit that referenced this pull request Sep 2, 2026
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
… two compiler_tests failures on this PR's run are main's, inherited through a merge ref pinned at 06:47Z against bb96afa

The merge ref CI built for f3839b2 was pinned at push time against main as it
then stood (bb96afa), which carried #9886's stale mirror. That is the exact
subject #10017 repaired at 06:56Z, nine minutes after this PR's run started.

Evidence, checked rather than assumed:
  - main at bb96afa, run 33596615712: 'test result: FAILED. 642 passed; 2
    failed' with render_rust_applied_type_routes_qualified_base_through_leaf_name
    and shell_service_unmodeled_output_key_refuses -- byte-identical counts and
    identities to this PR's rust-unit-tests failure.
  - #10017's own body names 'regen FAIL generated surface drift:
    compiler_tests.rs, std_realization_schedule.rs' reproduced on 4059156 and
    bb96afa -- the same two files this PR's build job reported.
  - This branch changes two .dag files and no Rust: git diff origin/main...HEAD
    over compiler_tests.rs and std_realization_schedule.rs is empty.

Merging rather than rebasing per the squash-merge policy, and pushing after
main's last move so the merge ref is recomputed against the repaired base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W377Pq4Tp5eQjGBXtPQEoM
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
…y-koi-286

Integration only: no claim of this lane is changed, extended, or repaired.

Corrects an incomplete staging in 1eeeecb. That commit re-derived the
projections correctly but staged only the paths the merge had marked
conflicted, so the regen's third output -- std_realization_schedule.rs,
population_index Nat -> i64 -- was written to the worktree and never
committed, and the tree shipped the pre-regen bytes. A conflict list is not
a census of a regeneration. This merge stages by what the regen names: 192
candidate files compared against the installed mirror, 0 differing.

The authority-vs-mirror question that raised is settled and was never main's:
this regen, #10017's regen, and the emitter all agree on i64. main was
authority-behind on that file at bb96afa and #10017 repaired it.

Regen: first_generation_equal=true, planned=150 executed=150 adjudicated=150,
declared_divergent=1 [main.rs] (not installed).
Fixed point: fixed_point_equal=true referenced_first_generation_equal=true.
Doc projections needed no regeneration -- neither side changed DESIGN.md or
docs/design-ledgers.md over the merge base.

compiler_tests.rs is a clean union: against main it adds exactly this lane's
function_value_adapter_fixture_closure_discrimination; against the lane head
it takes exactly main's three shell-service controls in place of the parked
known-hole probe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdxzwWekWhHR2FCTTf8a1b
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
…serted the pre-wall shape

Part 1 of the main-is-red item. #9886 produced ONE root cause with TWO symptom
classes, and #10017 closed the first. This closes the second. No emitter changes.

WHAT WAS RED. `cargo test --release -p v1-compiler --lib` on main: 642 passed,
2 failed. Both failures are in the GENERATED src/v1/stage0/src/compiler_tests.rs.

  render_rust_applied_type_routes_qualified_base_through_leaf_name -- ALREADY FIXED
  by #10017, verified here rather than assumed. #9886's stale mirror deleted two
  lines from the TEST BODY, `env_value.unit_variant_index_observed = true` and the
  same on populated_env_value. Counting that string across the three refs gives
  fb481ae=2, 4059156=0, 52aac48=2. Without the observed flag the env
  carries no unit-variant evidence, so render_rust_applied_type CORRECTLY refused
  with a located compile_error! instead of emitting i64. The emitter was right and
  the regenerated test was wrong.

  shell_service_unmodeled_output_key_refuses -- what this commit fixes. It is a
  test #9886 ADDED, and #10017's mirror repair never touched it, so it is a
  separate defect rather than a second face of the stale mirror.

WHY IT WAS UNSATISFIABLE THE DAY IT LANDED. It did
`.find(|f| f.path == "src/probe.rs").expect("service module must emit src/probe.rs")`
and then looked for the refusal text INSIDE that file. No such file exists, by
construction: v1.compiler.compile emit_artifact returns
`EmitResult { files: [], diagnostics: unmodeled_transports }` when a transport
diagnostic fires -- the empty file list is PAIRED with a blocking diagnostic, not
standing alone. §5 was already satisfied; the compile refuses, typed
(TransportEmissionNotModeled) and located (span: ch.span, at the FIELD, with the
individual key in missing_realization_fact). #9886 wrote the wall and the test in
one PR, and the test asks for the shape the wall replaced.

THE SHAPE IT ASKED FOR IS A FILED DEFECT CLASS, twice over, which is why the fix
is not to make the emitter emit the file.
  - gunbc.recurring_failure_mode `accepted_source_emits_uncompilable_target`:
    "the .dag graph is the authority and Rust is one realization, so 'rustc
    catches it' is exactly the outsourcing this project exists to end."
  - 05_emit.dag's own annotation records that this exact shape was TRIED and filed
    as `refusal_deferred_to_emitted_runtime`: "7 files emitted, 0 diagnostics
    beside a panic!(): the line did not stop, the compile reported success, and
    the refusal was deferred to a runtime nobody reads until production."

WHAT THE TEST NOW ASSERTS -- through the wall, not about the bytes:
  1. an error diagnostic of variant TransportEmissionNotModeled exists
  2. its rendered message names `not_a_channel` AND `has no modeled channel`
  3. no `src/probe.rs` among r.files -- the line STOPPED rather than reported
     and continued
  4. no emitted file anywhere contains `stdout.clone()` -- #9886's own
     fall-through assertion, widened from one file to all of them
Its positive control is the sibling shell_service_output_projection_binds_each_
declared_channel: same fixture with every key modeled, zero diagnostics,
src/probe.rs emitted. So "no file" here is the refusal firing and not an emitter
that never emits for services.

RED CONTROLS, EXECUTED, in an isolated detached worktree so nothing here was
edited mid-run. Two mutations of the seed, each rebuilt and run:
  - delete the wall's diagnostic (unmodeled_shell_transport_diagnostics returns
    []): conjunct 1 goes RED, "must refuse with TransportEmissionNotModeled.
    Got: []".
  - remove only the line-stop in emit_artifact, then neutralise conjuncts 1 and 2
    so the mutant is judged by the file conjunct alone: conjunct 3 goes RED with
    ["Cargo.toml", "src/lib.rs", "src/main.rs", "src/probe.rs", "src/v1_rt.rs",
    "src/dry_run.rs", "src/emitted_population.rs"] -- seven files emitted, which
    is the ledger's recorded pre-wall shape reproduced by execution.
So neither the typed-refusal half nor the line-stop half is a passenger.

AUTHORITY ONLY. The edit is src/v1/compiler_tests_rust.dag; the two .rs files are
its regeneration and were installed from the candidate tree, never hand-edited.
Regenerated across BOTH generations, since compiler_tests.rs is rendered from the
running seed's baked string: regen -> install v1_compiler_compiler_tests_rust.rs
-> REBUILD claim_executor (grep of the new binary confirms it bakes the new
string) -> regen -> install compiler_tests.rs -> regen, which reports
`first_generation_equal=true planned=150 executed=150 adjudicated=150`. The
rebuild is the load-bearing step: a regen that greened against the file it just
wrote would prove nothing.

Neither 05_emit_rust.dag nor 04_resolve.dag is touched, so the two lanes working
in those files are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSP89XiSm2YMnUvSwSR1ct
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
… homes

The falsifier PASSED at c9ee149: moved value carried once, former value zero
times, HTTP 500 from a real thread-CPU breach at 52176400ns against a 50ms bound,
tree restored, worktree removed, parent untouched. So the two prose artifacts
that stood in for it can go.

evaluation_budget_consequence_falsifier_steps() hand-described the same
transaction the instrument now performs and adjudicates. Two writable sequences
for one procedure drift in either direction, and the one made of prose cannot
fail. What replaces it is a pointer to the entry point.

The plan document is deleted by the cut it planned, which is what it said would
retire it. Its content has executing homes rather than a summary: the inverse
quality order and the no-floor law are annotations on std.evaluation_budget's own
arms; the terminal consequence law is the sealed host carrier plus the generated
projection; the bridge lifecycle is the seed-growth justification and the two
retention rows; the acceptance receipts are the instrument.

Not mine, and checked rather than assumed: rust-unit-tests is red on this branch
AND on main's own runs, on the same test (shell_service_unmodeled_output_key_refuses,
645 passed / 1 failed both places). The only difference my branch makes to that
job is 142 ignored against main's 141 -- exactly the one #[ignore] test this work
adds. #10017 names main's red as its subject.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176SNKT2W9dH3kRX5jp3Nga
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
Integrating #10005, #10014, #10017, #10024 and #10025 so this branch is judged
against the base in use rather than 4605989. No overlap with the files this
branch touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
… seed

The generated-artifact driver refused src/v1/stage0/src/v1_compiler_emit_rust.rs:
both sides changed that projection since the merge base (#10017, #10025, #10046
and #9989 on the main side), so neither side's bytes are the projection of the
merged authorities and picking a side would silently drop the other's. Regenerated
rather than resolved.

The seed had to come from main's mirrors to build at all. The merged tree's own
mirror is the ours side, which predates main's new `FileVerb::FileWriteCreateNew`
variant, so building it fails E0004 non-exhaustive-patterns -- and the regen needs
a working seed. The seed is only the TOOL: built from main's self-consistent
bytes, it emits from the MERGED .dag authority, which carries this branch's
constructor. Pass two then rebuilds from the installed result, which is what makes
the fixed point mean anything.

EVIDENCE, two passes as the driver's own instructions require, because pass one
runs a binary that predates the change it emits and can self-verify at divergence
0 for the wrong reason:

  pass 1  build from main's seed -> FAIL generated surface drift: v1_compiler_emit_rust.rs
          installed 1 file; main.rs skipped (declared_divergent=1, expected)
  pass 2  rebuild FROM the installed seed -> first_generation_equal=true, rc=0
  census  every file in the candidate tree vs the installed mirror: 222 compared,
          0 differing -- the regeneration is the subject, not the conflict list
  fixed point  --required-regen-fixed-point rc=0

The tree committed here is the tree those checks ran against, established by
content and not by which paths a patch happened to carry: sha256 of all 238 .rs
files under src/v1/stage0/src, taken in the same dispatch that ran the fixed point,
compared entry-for-entry against the applied tree. 238/238 identical, both
directions, so a file present on one side and absent on the other would have been
as loud as a hash mismatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9q7HZqy1inoJYfnNdBB5J
gunbai-bot Bot added a commit that referenced this pull request Sep 3, 2026
… proves it moves (#9981)

* ROOT-2B: one cooked-literal escape, three declaration routes, and ownership by surface cardinality

The evaluation-budget consequence projection needed a Rust string escape, and the
rule was already authored twice byte-identically. It moves to
extdeps.languages.rust.string_literal as escape_rust_cooked_string_literal_body:
"cooked" because Rust also has raw literals this rule does not serve, and CR ->
\r added because both predecessors escaped LF only, so an isolated CR passed
through unescaped. Seven exact-output witnesses; removing the CR pass reds
exactly the two CR witnesses and leaves the LF control green.

The wet-actuator family now carries a declaration route per row -- PathIncluded,
MacroUsePubModSpliced, PlainPubMod -- and each arm drives a different population
rather than labelling rows: the plain-render basenames, the lib.rs root modules,
and the filename exclusion. The retained population keeps its name and meaning,
because stage0_executable_assembly consumes it as the retained-source classifier
and a generated module has no retention row to find.

That surfaced a module the assembly census could not name: a main-wet-generated
module compiled by the host shell is neither partition-owned, nor an ordinary
emitted mirror, nor hand-retained. WetActuatorLibModuleOwner is that arm, and it
owns the macro-spliced dispatch module too -- which had been unowned all along.

Ownership is now decided by counting ACTIVE SURFACES instead of walking an
ordered chain of remembered pairs. The chain had a hole: an emitted-and-retained
module resolved as retained because is_retained was tested first. That pair now
refuses and stays enrolled as its own control. Partitioned-and-emitted is NOT
two surfaces -- the partition row says which package compiles the mirror -- and
the first run of the rewrite proved it by refusing both fixture partition rows.

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

* Regenerate the stage0 mirror, enrol the escape witnesses, and seal the refusal behind a module

THREE FIXES, ONE OF WHICH CI FOUND AND TWO OF WHICH REVIEW DID.

CI's red was real and my commit caused it: adding a registration row changes
gunbc.stage0_crate_layout_generated, and the SEED MIRROR of that module
(gunbc_stage0_crate_layout_generated.rs) is what emit_lib_rs_from_paths reads to
place the pub mod line. main_wet regenerates the .dag projection; it does not
regenerate the Rust mirror, so the emitter kept answering from the old block and
lib.rs drifted against it. Regenerated through the seed emitter and verified
BOTH gates: first_generation_equal=true and fixed_point_equal=true.

The cycle is worth recording because it bites once per generated module: the
emitter that must place the declaration is BUILT FROM the mirror that must
already contain it. Emitting before rebuilding produces a lib.rs missing the
module, and that state is not silent -- the seed refuses to compile with E0433
on the constant's own path, which is the module-exposure wall doing its job.

The seven escape checks were plain `fn`, so the discovery bridge never enrolled
them: my runs were valid measurements of declarations no gate would ever
execute. They are `test fn` now, individually rather than folded into one
aggregate, so a regressed substitution still names its own cell.

ServeBudgetRefusal was called sealed and was not. Rust privacy is module-scoped,
so private fields plus one associated constructor left every line of cli_run --
and every descendant -- free to write the struct literal with four favourable
values and no InterpError in sight, which is the fabricated refusal the carrier
exists to forbid. It now lives in its own child module behind free functions, so
the parent can render one and cannot assemble one. Free functions rather than
methods for a second reason the closure host already recorded: an impl method
has no DeclarationRef spelling, so it cannot be cited at all.

The call site also classified twice -- once for the guard, once under expect.
One match now produces one opaque consequence.

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

* The registry guard is real and one step removed, and the product falsifier is named as steps

TWO ROWS, BOTH WRITTEN FROM A MUTATION THAT WAS RUN.

Deleting the evaluation-budget artifact from generated_artifact_registry and
running the gate produced EXACTLY ONE finding, and it was .gitattributes drift:
the merge-driver enrollment projection enumerates registry members, so dropping
one changes its bytes. Nothing said the thing an author would want said -- that
a committed file at a generated location is now adjudicated by nobody. So the
guard holds by side effect, and an artifact excluded from that projection for
any reason would leave the registry silently. The stall row records the rung
that measurement supports, and its trigger names the direct capability rather
than one more artifact.

The product falsifier is recorded as STEPS, not as numbers. Every step is an
existing entry point; the only new thing is the one-token perturbation of the
authority, and that perturbation is what makes the other four discriminate at
all -- unperturbed, they are green whether or not the seed reads the projection,
because the value it would have chosen independently is the same value.

Step 3 rebuilds before serving for the reason the merge driver already records
about regen, and which this construction paid to learn twice: a binary that
predates the change it must express reports a confident green for the wrong
reason.

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

* Regenerate the rust-source-type-bindings mirror main landed without it

Not my change and not my defect: #9949 corrected the String Proven row's prose
in gunbc.rust_source_type_bindings and its seed mirror was never regenerated, so
the drift arrived here with the merge. Reproduced locally after integrating main
-- one file, prose only, no behavioural delta -- and regenerated through the seed
emitter, verifying from the INSTALLED seed rather than the binary that emitted
it: first_generation_equal=true and fixed_point_equal=true at this merge.

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

* Close the rendering bypass, refuse duplicate wet registrations, and join the live populations

THREE REVIEW FINDINGS, EACH A HOLE THE PREVIOUS FIX LEFT OPEN.

Sealing the constructor stopped a caller assembling a refusal with a chosen
code. It did not stop a caller holding a LEGITIMATE refusal from passing an
encoder closure that ignores its argument -- the machine body took
`json_string: impl Fn(&str) -> String` from the parent, so the code was still
caller-selectable one level up. Construction and rendering are two boundaries
and only one was closed. The child now calls the ancestor's private encoder
directly, and the three functions narrow from pub(crate) to pub(super) so their
spelling states the boundary they actually have.

The wet route lookup returned a List, which is a decision to tolerate
duplicates: surface counting read any nonempty result as one surface, and owner
construction folded the routes to whichever came last. So the same basename
registered twice was accepted silently, and two rows disagreeing about the route
resolved by authoring order. WetRouteAbsent / WetRouteUnique / WetRouteDuplicate
makes that a state the caller must answer, with both controls -- same route
twice, and conflicting routes -- because they prove different things. No compile
error stands behind either: a macro-spliced duplicate contributes no lib.rs line
for rustc to reject.

The fifth owner arm was fixture-executed and not live-consumed, and
stage0_lib_declared_module_basenames was definition-only, which is the inert
shape. One authority-to-authority join now requires every live wet registration
to appear exactly once in the root-module population, resolve through the wet
arm, and keep its exact route -- with the two specimens named so it cannot pass
vacuously on a population that quietly lost a member. It does NOT claim the
no-retention-row property: the live census binds the real roster, so that arm
cannot be posed there, and it stays where it can be shown.

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

* Arm and invoke from one contract, and refuse a refusal that names another subject

The host took the executed function and the budget limits as two independently
supplied values, armed the evaluator from one and invoked the other. Today's
call site passes the same `function` to both -- but that is a fact about one
call, not about the interface, and the interface is what the next caller
inherits. A process armed for one entry while evaluating another produces a
refusal whose `entry` names the wrong function: a located diagnostic pointing
somewhere true-looking and wrong, which is worse than no diagnostic.

ServeArmedContract binds the subject and its limits once, before the listener
serves. serve_contract_entry is the only way to name the evaluated function at
this seam, and the startup announcement, the arming call and the invocation all
read that one value; `function` and `serve_budget` are not consulted again.

It is deliberately NOT the full ContractIdentity<Subject>. Surface and epoch
have no host consumer at this seam, and minting fields nothing reads is the
richer-type-name-as-safety move DESIGN section 4b names as cosmetic.

The exceeded carrier is then checked against the armed contract before anything
is rendered. If the interpreter reports a refusal for another entry, arming and
evaluation had different subjects, so the path refuses instead of rendering a
diagnostic about a function this process never armed.

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

* The evaluation-budget consequence falsifier: a durable instrument for the whole transaction

The five-arm receipt existed only as a procedure I ran by hand and a list of
steps in a .dag row. A prose procedure is a second writable sequence beside the
thing it describes, and the first attempt at it proved the hazard: I misread a
`git fetch <sha>` line as evidence that the remote builder ignores the working
tree, and killed a valid run over it.

So the transaction is now one on-demand instrument that owns it end to end: bind
a clean parent and a disposable detached worktree, build the candidate tool
BEFORE perturbing anything, move exactly one authority literal (located in the
source, not sed-and-hope), require exactly one attributed drift, regenerate
through both generations, rebuild, supervise a real serve process in its own
process group, drive a real thread-CPU breach over the committed fixture, judge
status/code/entry/clock/limit and the moved-vs-former counts, terminate and
wait, restore, and re-verify that the parent's HEAD, tree, status and worktree
inventory are untouched.

WHY RUST AND NOT A MODELED ACTUATOR, measured rather than preferred: the model
has no vocabulary for a live child. extdeps.shell.exec runs one command to
completion and std.process_termination describes a process that ENDED -- no
handle, no readiness, no later termination. The two .dag-shaped options were to
invent a managed-process substrate whose only consumer is this receipt, or to
hide start/readiness/request/kill inside one opaque command where no fold can
read the adjudication. The out-of-band-actuation tell is about bypassing a
modeled operation that exists; here it does not.

Nineteen typed refusal classes, because the remedies differ: a dirty parent is
an operator problem, a wrong drift population is a defect in the bridge, and a
failed teardown means no verdict may be reported at all. A cleanup failure never
overwrites the experiment's own cause -- both are carried.

TimedOut, Signaled, SpawnRefused and Completed stay distinct, which is the
permanent form of a mistake this session made by hand: a loop that wrapped runs
in `timeout` and read any nonzero exit as a false verdict reported two green
witnesses as red.

process_group.rs is an extraction, not a new capability: the Codex session driver
already spawned into a group and signalled it. It is deliberately not reused as a
generic supervisor -- its flow waits for the child before signalling, which is
correct only because its protocol makes Codex exit, and would deadlock against a
server that stays alive. Both files carry seed-growth justification and retention
rows whose trigger names the capability that retires them.

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

* The falsifier's first run refused, on a defect in the falsifier

It compared `git worktree list` before and after and refused
ParentCheckoutChanged. Correct by its own rule and wrong as a fact: this
repository is shared, other sessions add and remove worktrees while the
transaction runs, and none of that is something this instrument caused or can
control. A check whose red is dominated by events outside its subject is not a
wall -- it is a source of false refusals that trains a reader to ignore the real
one. What the transaction owns is its own worktree, so that is what must be gone,
and a survivor is now WorktreeCleanupFailed rather than a claim about the parent.

The same run exposed a second defect it could not report: when cleanup refused,
the match arm replaced the outcome wholesale, so a PASSING product transaction
whose cleanup failed came back saying only that cleanup failed. The terminal now
carries PassedWithCleanupFailure -- still not a pass, because an instrument that
leaves residue has not finished, but the receipt survives beside the cause.

Both are defects the instrument found in itself by running, which is the point of
running it.

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

* Delete the prose procedure and the plan, now that both have executing homes

The falsifier PASSED at c9ee149: moved value carried once, former value zero
times, HTTP 500 from a real thread-CPU breach at 52176400ns against a 50ms bound,
tree restored, worktree removed, parent untouched. So the two prose artifacts
that stood in for it can go.

evaluation_budget_consequence_falsifier_steps() hand-described the same
transaction the instrument now performs and adjudicates. Two writable sequences
for one procedure drift in either direction, and the one made of prose cannot
fail. What replaces it is a pointer to the entry point.

The plan document is deleted by the cut it planned, which is what it said would
retire it. Its content has executing homes rather than a summary: the inverse
quality order and the no-floor law are annotations on std.evaluation_budget's own
arms; the terminal consequence law is the sealed host carrier plus the generated
projection; the bridge lifecycle is the seed-growth justification and the two
retention rows; the acceptance receipts are the instrument.

Not mine, and checked rather than assumed: rust-unit-tests is red on this branch
AND on main's own runs, on the same test (shell_service_unmodeled_output_key_refuses,
645 passed / 1 failed both places). The only difference my branch makes to that
job is 142 ignored against main's 141 -- exactly the one #[ignore] test this work
adds. #10017 names main's red as its subject.

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

* Restore the prose procedure and the plan: the deletion was premature and unparseable

Two independent things said the same thing about 3fd5892.

The parser said it first, ten times. Deleting
evaluation_budget_consequence_falsifier_steps() left the annotation block that
EXPLAINED the deletion standing at end-of-file, and DESIGN section 4c attaches an
annotation to the module item that FOLLOWS it. Prose about an absence has no
subject, so required-witnesses-floor refused the parse phase — which also took
namespace-wave-admission and floor down with it, since neither runs without an
index. Reproduced locally with the same ten diagnostics before repairing, and the
repaired tree reports `parse OK 4516 file(s) parse-clean`.

The reviewing authority said it independently: the prose step list and the plan
document delete only after the executable terminal has actually passed at the
exact current head, and its five finding-5 seams are still open, so no such
terminal exists yet. Both are restored verbatim from 3fd5892^ and will be
deleted in the same commit that carries the qualifying receipt, not before.

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

* Close the five instrument seams: teardown, observation, readiness, port, provenance

The review's finding 5 was not that the falsifier fails, but that several states it
labels "completed", "ready", "settled" and "bound" do not yet mean what the labels
claim. Each seam below is one such label.

1. TEARDOWN NOW ASKS ABOUT THE GROUP. terminate_process_group polls kill(-pgid, 0)
   to ESRCH after reaping the leader -- an unreaped zombie is still a member, so
   polling first would report presence for a process already dead -- and escalates
   to SIGKILL on the group whenever anything survives, INCLUDING when the leader
   exited cleanly. It returns ProcessGroupTermination { leader, residue,
   escalated_to_kill }; only GroupAbsent is success, and any errno that is not
   ESRCH is ResidueObservationFailed rather than absence, because an instrument
   that cannot see the group has not established that it is gone. The host's wall
   now asks group_is_gone() instead of reading the leader's status. PID reuse after
   the reap fails in the safe direction: a recycled pgid reads as presence, which
   refuses.

2. THE OBSERVATION ALGEBRA IS EXHAUSTIVE. spawn-refused / wait-failed-after-spawn /
   completed / signaled / timed-out-and-terminated / timed-out-with-termination-
   failure, with the teardown verdict carried on both timeout arms. WaitFailed is
   no longer mapped onto SpawnRefused -- that said a process which ran and could
   not be observed had never run, and the remedy is the reverse -- and it now tears
   the child down, since a failed wait established nothing about it. The stdout and
   stderr drain threads are joined on EVERY arm; they were dropped on three of
   four, discarding the child's own account of exactly the interesting cases.

3. READINESS IS A JOINT TYPED OBSERVATION, not a Boolean. ServeReadiness is Ready /
   ExitedBeforeReady / ReadinessTimedOut / ReadinessObservationFailed, and Ready
   requires all three of: the owned child still running, its exact announcement,
   and a connection to the port IT named. The old shape derived "timed out" versus
   "exited before ready" from the TERMINATION attempt rather than from the
   readiness interval, so a server that stayed alive and never listened was
   labelled as having exited. Teardown is now adjudicated separately from
   readiness, and neither can overwrite the other.

4. THE PORT IS THE OS'S CHOICE. 8300 + pid % 400 collides across pids and can be
   held by an unrelated local listener -- which, under a connect-only readiness
   wall, could SATISFY readiness and send every later disagreement to the subject's
   account. The child is now started with --port 0 and announces what it bound.
   That required a seed correction: gunbc serve announced the REQUESTED port, which
   is a fib whenever the OS chose, so it now announces listener.local_addr(). Every
   character of the announcement is pinned except the port -- entry, release
   revision, both budget arms -- so matching it binds the listener to the contract
   this run armed rather than establishing that something, somewhere, listens.

5. THE RECEIPT CARRIES THE PROVENANCE IT CLAIMED. The source has always said the
   pre-perturbation binary's digest identifies the producer that noticed the drift;
   it said so in prose while the receipt held no digest at all. Four are now
   recorded -- orchestrator, dry-gate gunbc, serving gunbc, generated artifact --
   and the two gunbc digests MUST differ, since the binary embeds the generated
   consequence that just changed; equal digests refuse as SubjectBinaryUnchanged
   rather than letting the old producer answer for the new value. The dry-gate
   digest is taken while that binary is still the only one to have existed at that
   path, because the rebuild overwrites it in place.

   The HTTP half is typed rather than grepped: headers are retained and
   Content-Type must be application/json; charset=utf-8, the body is parsed by a
   flat-object decoder that REFUSES a shape it does not model instead of skipping
   it, and the subject's own stderr diagnostic must agree with the body on entry,
   clock, elapsed and limit. A substring search could not tell a field from the
   same text inside another string, nor a missing field from a malformed document.

Checks clean and clippy --all-targets -D warnings clean. The qualifying exact-head
run is next; the prose procedure and plan document delete in the commit that
carries its receipt.

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

* Delete the prose falsifier procedure and the completed plan, with its annotation

The second attempt at a deletion the reviewing authority had already accepted in
substance. The first attempt (3fd5892) removed the function and left the
annotation block that explained the removal standing at end-of-file, where DESIGN
4c has nothing for it to name; that is what refused the parse phase ten times and
took namespace-wave-admission and the floor down with it. Prose about an absence
has no subject, so it goes with the thing it described.

evaluation_budget_consequence_falsifier_steps() hand-described the same
perturb -> gate -> regenerate -> build -> serve -> judge -> restore transaction
that v1_compiler.evaluation_budget_consequence_falsifier_host now performs and
adjudicates. Two writable sequences for one procedure drift in either direction,
and the one made of prose cannot fail. DESIGN section 6 says a measurement worth
re-deriving is worth an entry point rather than a description of one; the entry
point exists, so the description is deleted rather than demoted to documentation.

The plan document is deleted by the cut it planned, which is what it said would
retire it. Its laws have executing homes: the inverse quality order and the
no-floor law are annotations on std.evaluation_budget's own arms, and the terminal
consequence law is the sealed ServeBudgetRefusal carrier plus the generated
projection. Nothing links to it.

This lands FIRST rather than last, against the sequencing I was given, for a
reason that sequencing did not anticipate: the receipt binds subject_commit and
subject_tree, so a qualifying run can only ever describe the tree it ran on. Were
the deletion to follow the pass, it would produce a different tree and invalidate
the very receipt it was waiting for. So the deletion is made first and the
qualifying run is taken ON this head. If that run does not pass, this commit is
reverted rather than kept.

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

* Reopened seams 1, 2 and 5: pinned-identity teardown, bounded drainage, whole-document JSON

Three real defects, all mine, all found by review rather than by execution.

SEAM 1 — I HAD WRITTEN AN ANNOTATION THAT WAS FALSE OF ITS OWN CODE. The previous
terminate_process_group reaped the leader and THEN signalled the numerically
matching group if anything appeared present. Once the leader is reaped its pid is
free for reuse, so that signal could land on an unrelated group that merely
inherited the number: wrong-subject actuation, not a safe over-approximation. The
comment beside it said PID reuse "fails in the safe direction", which is true of
REFUSING on presence and false of the signalling the code actually did. The
annotation described the design I had in mind rather than the one I wrote, and
being confident it was safe is what stopped me re-reading it.

The rewrite makes the order the safety argument. An unreaped leader -- running or
zombie -- keeps its pid allocated and therefore keeps the group identity pinned, so
every signal now happens BEFORE the reap, and after the reap this function neither
signals nor observes the group by number: a survivor is refused to the caller
instead. Membership is read from /proc rather than inferred, because a signal
cannot ask who is in a group, only act on everyone who is; the fields are located
from the last ')' since comm may itself contain spaces and parentheses. The leader
is excluded from the residue because it is adjudicated separately by its exit
status, and residue answers the different question of whether the server's HELPERS
outlived it -- the ones that would still hold the port next run.

SEAM 2 — THE DRAIN COULD HANG EXACTLY WHERE IT MATTERED. observe joined
read_to_string threads unconditionally. A descendant that survived teardown still
holds the write end of the pipe, so EOF never arrives and the join blocks forever
-- on precisely the arm where teardown had already failed and the child's output
was the thing worth reading. Draining is now bounded and its result is typed:
StreamOutcome is Closed or Unfinished, so a truncated read can never be judged as
a complete one. WaitFailed becomes WaitFailedAfterSpawn and carries the typed
ProcessGroupTermination rather than a formatted debug string; the timeout arms
already preserved that algebra and this arm was throwing it away into prose.

SEAM 5 — THE DECODER TOOK THE LAST ANSWER AND IGNORED THE REST OF THE DOCUMENT. A
body with two `code` members silently let the later win, and bytes after the
closing brace were never looked at, so a valid-looking prefix could carry anything
at all behind it. Duplicate members now refuse by name -- including a duplicate
that changes type, which would otherwise put one copy in each map and collide in
neither -- and the whole document must end after the closing brace.

Those two walls ship with their REDs, as ordinary --lib tests rather than inside
the tens-of-minutes #[ignore] transaction: a duplicate that would have won, a
cross-type duplicate, trailing content, an unmodelled nested shape, plus the
positive control and a trailing-whitespace case so the wall cannot fire on
well-formed responses. All six execute on the merge path. 6 passed.

clippy --all-targets -D warnings clean.

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

* An external prune of the experiment's worktree is not the subject's build failure

The falsifier refused at 17f3808 with CandidateProductBuildFailed, and the build
had genuinely failed: the seed's build script hard-requires `git rev-parse HEAD` to
stamp the source commit, and inside the experiment's worktree git had stopped
working. But nothing about the subject caused it.

A linked worktree is a directory whose `.git` FILE points at an admin directory
under the common `.git/worktrees/`. Any process sharing the repository can delete
that -- `git worktree prune` does, routinely -- and on a machine where many sessions
share one clone it is not even unusual. The directory survives; only its
registration dies. Measured after the refusal: the worktree directory was still
there, its `.git` still read `gitdir: .../worktrees/ebc-falsifier-305713`, and that
directory was gone, so `git -C <worktree> rev-parse HEAD` answered `fatal: not a git
repository`. The paired WorktreeCleanupFailed ("is not a working tree") is the same
event seen from the other end -- two symptoms, one external cause.

So the instrument charged the subject for something the subject did not do, which is
the exact failure this whole construction exists to prevent. The three build sites
now ask whether the experiment's own environment was still intact BEFORE attributing
the failure, and refuse as ExperimentWorktreeUnregistered when it was not. The
ordering is the point: ask first, attribute second.

The check resolves the `.git` pointer rather than asking git from inside the
worktree, because a call from inside fails for this reason and for several unrelated
ones and therefore cannot discriminate between them.

This is a real hazard rather than a one-off: the shared clone here carries over 130
worktrees, and any session's prune reaches all of them. The durable fix is a plain
clone, which no prune can unregister; that is a larger change than this PR should
carry, and the typed refusal now names the condition when it recurs.

clippy --all-targets -D warnings clean.

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

* One authority for the budget-refusal diagnostic text

review 59017 found the template "serve: refused {} on {} clock: elapsed_ns={}
limit_ms={}" authored twice: once by the emitter and once by the falsifier, which
re-minted it to match serve's stderr. One fact in two places -- an edit to the
emitter silently stopped being checked by the falsifier.

The obvious consolidation was unavailable and it is worth saying why. The
falsifier holds four scalars parsed from the response body, not a
ServeBudgetRefusal, and that type deliberately has no constructor over loose
values -- the whole point being that a boundary which merely CHOSE the same text
must not be able to render a refusal. Handing the falsifier a from_parts would
have removed the duplication by reopening the hole the type exists to close.

So what is shared is the TEXT RENDERING over scalars, not the refusal. A caller
holding four scalars can now describe what a refusal WOULD say and still cannot
make the boundary emit one.

One property is deliberately given up: the falsifier's private copy would have
caught an unannounced change to the emitter's template. That was never its
subject -- it checks that the body's fields agree with the printed line -- and a
template checked against a copy of itself is a change detector rather than an
oracle (§5). The §3 fork is the more expensive of the two.

Verified: cargo clippy --all-targets -- -D warnings green.

* Teardown: git forgetting the worktree is not the directory surviving

The falsifier reached its verdict at 68f6667 and reported
PassedWithCleanupFailure: `git worktree remove --force` exited 128 with "is not
a working tree" while the directory was still present with its contents. The
report was CORRECT -- cleanup really had not happened and the directory leaked --
so this is not a false alarm being silenced.

The cause is that this is a SHARED checkout. A `git worktree prune` run by any
other session deregisters this instrument's worktree mid-transaction, after which
`worktree remove` refuses for a directory that still exists. That is an
environmental fact about the registry, not a fact about the subject under test --
the same distinction `ExperimentWorktreeUnregistered` already draws on the build
path, now drawn on the teardown path instead of being re-invented as a second
notion of the same thing.

The obligation this function owes is that OUR directory is gone and no
registration of ours survives. Where git has already forgotten the path, finishing
that obligation is ours, so the tree is removed directly.

This does not widen the refusal. The detection is narrow -- nonzero exit AND
git's specific "is not a working tree" text -- every other failure still refuses
with the observation attached, and every post-condition below runs unchanged: a
surviving directory, a surviving registration, or a mutated parent checkout each
still refuse. The path removed is this run's own generated scratch directory, so
no other worktree is addressable by it.

Verified: cargo clippy --all-targets -- -D warnings green.

* Three absorbing fallbacks in the falsifier host become typed refusals

review 59127 found three places where this host answered with a default instead
of refusing. All three are the §5 tell -- the failure arm widened rather than
stopped the line -- and all three are in an instrument whose entire purpose is to
be believed when it says the consequence moved.

1. ABSENT RESPONSE MEMBERS. `fields.string("code").unwrap_or_default()` and its
   four siblings collapsed "the body did not carry this member" into "" or 0, and
   fed that into the comparisons. The refusal that followed named the wrong fact:
   `code ` rather than "the body carried no code", and a defaulted limit_ms of 0
   invents a number the server never sent. Now `ResponseMemberAbsent { member,
   body }` at the extraction site.

2. GIT OUTPUT THAT WAS NEVER COLLECTED. `stdout_of` answered `String::new()` for
   every non-Completed observation, so a timed-out or signalled `git rev-parse`
   produced an empty HEAD that then compared unequal and was reported as
   ParentCheckoutChanged -- a located, confident, WRONG culprit. Every site that
   DERIVES a verdict from git's stdout now goes through `git_stdout`, which
   refuses with `GitObservationFailed { what, observation }`. `stdout_of` remains
   only where `completed_zero` has already adjudicated the observation on the line
   above.

3. AN UNDECLARED CARGO. `env::var("CARGO").unwrap_or_else(|_| "cargo")` silently
   PATH-searched. This instrument's claim is same-identity evidence -- that the
   binary it judges came from the source it perturbed -- so building with a
   different toolchain than the one running the test is exactly the substitution
   it exists to detect. Cargo always sets CARGO for a test process; its absence
   means the run is not shaped the way the receipt assumes, and now refuses.

Verified: cargo clippy --all-targets -- -D warnings green.

* Dissolve stage0_wet_route_is_present into the coproduct it was standing in front of

review 59152 found this Boolean predicate re-encoding which WetRouteResolution
variants constitute presence, and it is right that the shape is wrong even though
the two behaviours agreed.

A predicate answering "is a wet route present" and a match answering WHICH
resolution it is are two encodings of one fact (§2, "model a concept once"), and
the predicate is the one that can drift: it could admit a route the match then
calls absent, or exclude one the match would have owned. At the owner-resolution
site the guard was followed immediately by a TOTAL match over the same value, so
it decided nothing that the match did not decide again.

The coproduct is now matched directly at both sites. At the owner resolution the
ABSENT arm carries the rest of the chain -- extracted to
`stage0_resolve_non_wet_module_owner` rather than inlined, because the ordinary
retained/emitted decision has nothing to do with wet routes and burying it inside
a wet-route arm would make a reader answer a question about actuators to find it.
Guard and match can no longer disagree, because there is no guard.

Where the active surfaces are counted, a DUPLICATE route still counts as a
surface, so the cardinality answer stays a property of the module; the ambiguity
is refused where the owner is decided rather than being dropped from the count.

The rationale moved above the declarations: §4c admits annotations at module-item
grain only, and my first attempt put them inside the bodies, which the compiler
refused with 6 diagnostics.

Verified: the module compiles with 0 blocking errors, and required-regen reports
first_generation_equal=true.

* Close the falsifier's observation and teardown integrity findings

Six findings from the reviewing authority's ruling on this head. All six were
verified against the tree before being accepted; all six were real, and two of
them are defects in my own earlier repairs rather than in the original code.

1. THE POST-REAP SIGNAL PATH STILL EXISTED. `await_serve_ready` called
   `child.try_wait()`, which CONSUMES the exit status, and then called
   `terminate_process_group`, which signals the pid and the group by number. That
   is the exact ordering `terminate_process_group`'s own annotation forbids, at a
   call site that violated its precondition -- so the seam I described as closed
   was open at one entry. Readiness now observes the leader through /proc without
   reaping (`observe_leader_without_reaping`), so the identity stays pinned and
   the reap remains last. A pid that has left /proc unreaped refuses rather than
   being signalled.

2. `GroupAbsent` COULD BE MANUFACTURED FROM AN INCOMPLETE OBSERVATION.
   `process_group_members` skipped a numeric process on any stat read error,
   malformed syntax, missing field, or unparsable pgrp. A process we cannot
   inspect might be in the target group, so skipping it let an unreadable /proc
   produce an empty vector and then "the group is gone". Only NotFound -- the
   process genuinely vanished mid-scan -- is still skipped; everything else
   refuses.

3. THE TERMINAL'S SUCCESS ARM WAS A PREDICATE OVER ONE FIELD. `group_is_gone()`
   consulted only the residue, so a leader that had TIMED OUT could coexist with a
   successful teardown, and every caller had to remember the omission.
   `ProcessGroupTermination` is now `Settled | Unsettled`, where `Settled` is
   unconstructible without BOTH an adjudicated leader and a completely observed
   absence.

4. THE STREAM TERMINAL COULD LIE, AND THE SERVE PATH COULD HANG. The reader
   mapped EOF and read failure to the same bare `return`, so a broken read became
   "complete output"; `snapshot()` answered "" for a poisoned lock. The reader now
   records WHY it stopped, `finish_within` JOINS rather than merely observing the
   thread finished, and `StreamOutcome` distinguishes Closed / ReadFailed /
   DeadlineExceeded / ReaderPanicked. The serve path's unbounded `finish()` is
   gone: teardown is adjudicated BEFORE the drain, because an unsettled teardown
   is exactly when a descendant still holds the write end -- draining first blocked
   forever on the arm that was supposed to report the failure.

5. MY OWN `git_stdout` REPAIR WAS INCOMPLETE. It accepted any `Completed`,
   ignoring exit code and stream terminals, so a nonzero `rev-parse` with empty
   stdout still produced a successful empty read -- the very failure the function
   was added to remove. It now requires exit zero AND both streams complete, and
   `completed_zero` carries the same join.

6. THE JSON DECODER DID NOT REQUIRE MEMBER SEPARATORS. It consumed a comma
   wherever it saw one and accepted a fresh key with none before it, so
   `{,"a":1}`, `{"a":1,}`, `{"a":1,,"b":2}` and `{"a":1 "b":2}` all parsed. A
   position state machine now requires exactly one separator between members. The
   body is also decoded with `String::from_utf8` rather than `from_utf8_lossy`:
   the response declares charset=utf-8, and repairing invalid bytes into U+FFFD
   would let a broken body be compared as though it agreed.

Also narrowed, not repaired: the receipt's digest fields claimed "which binaries
actually answered". `file_digest` hashes files at paths and does not inspect the
image a process loaded, so the heading claimed process-incarnation identity on
evidence establishing on-disk artifact identity. The claim is now stated as what
it is; binding the loaded image is named as the stronger thing not claimed.

EXECUTING EVIDENCE, on the ordinary merge path rather than in the #[ignore]
falsifier -- which exercises the happy path and so cannot establish that these
arms refuse. Eight `--lib` tests: malformed separators (with the valid object as
positive control), zero-exit-with-truncated-stream, nonzero git with empty stdout
(with an ordinary git read as control), a failing reader, a descendant holding the
pipe open, an unadjudicated leader, an unobserved residue, and a dead leader
observed unreaped while its pid is still addressable.

Verified discriminating rather than assumed: restoring the old `completed_zero`
widen turns exactly one of these red and leaves the rest green.

* The /proc enumeration error refuses too, and the refusal is now authorable

review 59209, BLOCKING and correct. I repaired the `stat` read failure in the
previous commit and left `Err(_) => continue` on the DIRECTORY ENTRY one line
above it -- the same class, in the same function, under a comment I had just
written saying every non-disappearance failure must refuse. An incomplete
enumeration could still produce an empty vector and therefore `GroupAbsent`,
certifying a teardown from a scan that never finished.

It now refuses, naming the incompleteness.

THE MORE USEFUL HALF OF THIS COMMIT. When the reviewing authority asked for a red
proving that an unreadable or malformed /proc entry yields observation failure
rather than absence, I reported that I could not author it: the real /proc cannot
be made to return a malformed `stat`. That was a claim about this function's
SIGNATURE, not about the class -- and DESIGN §4b says to ask whether a check's red
is authorable BEFORE concluding it is not, because a missing harness is a
next-rung trigger rather than a ceiling.

So the scan takes a root. Production passes `/proc` and nothing else ever does;
the parameter exists so a fixture can stand in, and it is not a policy knob.

Three tests now execute on the merge path: a well-formed fixture scan that finds
its members and reads a zombie's state (the positive control, without which every
refusal below would pass on a scan that refused everything); three unparsable
forms -- no comm terminator, too few fields, unparsable pgrp -- each refusing; and
the one arm that may still be skipped, a process whose stat is NotFound because it
genuinely vanished mid-scan.

Verified discriminating rather than assumed: restoring the skip on a missing comm
terminator turns the refusal test red, and its failure names the case.

* Close the remaining observation-integrity findings: identity, sealing, drainage, JSON

Six open items from the reviewing authority's ruling on the previous head. Each
was verified against the tree first; each was real.

1. THE POST-RELEASE SIGNAL PATH SURVIVED MY OWN REPAIR. I had fixed readiness to
   observe without reaping, but its LeaderVanished and ObservationFailed arms both
   became ReadinessObservationFailed, whose caller then called
   terminate_process_group -- so the path still refused only AFTER signalling the
   released number. The generic observer's WaitFailed arm had the same shape,
   under a comment saying the failed wait had established nothing about the child.

   A caller convention could not fix this, because the convention is what kept
   failing. `PinnedProcessGroupIdentity` is now the only way to reach a signal: it
   can be produced solely by an observation that found the process present and
   unreaped, and `terminate_process_group` takes it instead of a `u32`. Losing the
   identity no longer compiles into a signal. `ServeGuard::drop` obeys the same
   algebra -- it reaps its own handle and sends nothing -- because a drop cannot
   report, so an unprovable identity there would be the quietest possible
   wrong-subject actuation.

2. `Settled` WAS STILL WRITABLE. The builder joined the facts correctly, but the
   enum was `pub(crate)`, so any module could construct the success arm directly
   -- and my own positive test did exactly that, which means the test established
   what `build` happens to do rather than that bypassing it is impossible. The
   type is now a struct with a private state, so `Settled` is reachable only
   through `build`.

3. COMPLETE-OUTPUT AUTHORITY AT EVERY CONSUMER, not just in the helper.
   `stdout_of` is DELETED rather than fixed, because leaving the unsafe spelling
   beside the safe one is how call sites keep finding it. `completed_drained` is
   the single extractor. The source-status check no longer reports a git timeout
   as SourceCheckoutNotClean with an empty status; the dry-gate verdict no longer
   counts drift lines out of a truncated prefix; the deregistered-worktree
   exception no longer decides on an incomplete stderr.

4. THE /proc SCAN'S ITERATOR ARM. Split into a production wrapper
   (`process_group_members_at`) and a testable core over a FALLIBLE entry source,
   with the stat parser separated and made strict -- it now rejects a record whose
   own pid prefix disagrees with the directory it came from, a multi-character
   state field, and a missing comm opener.

5. THE DECODER STILL ACCEPTED NON-JSON: raw control characters inside strings and
   leading-zero numbers. Both refuse. The diagnostic previews sliced by BYTE
   offset, so a malformed multibyte document could panic the instrument while it
   was building the refusal meant to describe it; previews now count characters.

6. THE SEED-GROWTH AUTHORITY WAS FALSE. It named
   `await_listening`, which does not exist -- the declaration is `await_serve_ready`
   -- and the roster had not been re-censused for anything added since. It is now
   generated from the two modules' actual top-level declarations: 51 rows, up from
   17. Also corrected: the termination comment describing "three fields rather
   than one enum" above an enum, the stream heading describing an `Unfinished`
   variant that no longer exists, and two digest comments still claiming producer
   identity after the receipt narrowed to on-disk identity.

EVIDENCE, all on the ordinary merge path: 9 arms in the falsifier host and 8 in
process_group. New this commit -- a reader that actually PANICS when polled, so
the ReaderPanicked arm executes `join().is_err()` rather than being constructed by
hand; invalid UTF-8 response bytes; control characters and leading zeros; a
multibyte malformed document that must refuse without panicking; a failed
directory entry; a non-NotFound stat read failure; a non-numeric entry ignored; a
stat declaring a different pid; and the positive control the ruling named -- a
clean scan with no members reporting an empty group, without which a scanner that
refused every empty result would pass every RED.

Full suite: 715 passed, 0 failed. clippy --all-targets green.

* Review 59301: the evaluation budget's nanosecond fold carries the nanosecond carrier

evaluation_limit_nanos returned a bare Int and effective_nested_limit_nanos
took and returned two of them, in the module that imports Nanosecond from
std.measure and cites it as the canonical exact elapsed-time carrier. A wall
remainder and a cpu remainder type-checked against each other in the one place
that must never substitute them silently: the two clocks are separately armed
and separately assessed, and the smaller of a wall remainder and a cpu limit
bounds neither. Both now traffic in Nanosecond; the comparison unwraps at the
comparison, which is the idiom std.measure's own comparator uses.

Also finishes review 59257: two probe_route_is call sites survived at :640-641,
which is what the floor lane refused on (function not found in scope). The wet
lib-module population is three rows, not two, since gunbc_file_transport_generated
carries PlainPubMod — asserted by identity beside the count, and confirmed by
execution: required-regen reaches first_generation_equal=true only because the
emitter renders that basename's ordinary pub mod line.

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

* File the duplicate-match-arm compiler gap as its own failure-mode row

The class is the DUAL of exhaustiveness, not an instance of it: exhaustiveness
asks whether the union of the arms covers the domain, and gunbc does refuse
when it does not — the same file refused loudly for a MISSING
WetActuatorLibModuleOwner arm earlier in this PR. The unasked question is
whether each arm contributes an inhabitant no earlier arm already covers, and
a duplicate arm makes the union no larger, so an exhaustiveness check is green
by construction on exactly the source that carries the defect.

The measured grain is narrow and stated narrowly — two syntactically identical
closed-variant constructor heads at one match — because the general subsumption
question is a ratchet and claiming it here would let the narrow wall be cited
as coverage for the broad class. Rung found at: outside the ladder. Ceiling 3.
Trigger names the capability: a match-arm usefulness judgment that REFUSES the
covered arm, with a discriminating red, a positive control, and a retention
control over adding a variant.

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

* Enrol the generated-artifact registry-membership stall in all_guarantee_stalls

The row was declared and never rostered, so neither
every_live_stall_is_below_its_ceiling nor every_live_stall_names_a_next_rung_trigger
ever ran over it — a stall carrier whose own consumers could not see it.

Found by the four-tree merge census the authority asked for, not by reading the
diff: main's split of guarantee_rung_drop.dag into guarantee_stall.dag merged as
a RENAME, so git carried the row's DEFINITION across and the roster line, which
lived in a list main had rewritten, did not come with it. The census classified
that path ExactSideSelected_theirs, which is what sent me to look.

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

* Fix codex session process-group teardown ordering (#10147)

* Fix codex session process-group teardown ordering

* Refuse silent natural-exit overruns

* Document process-group termination grace policy

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: gunbai-bot[bot] <289086189+gunbai-bot[bot]@users.noreply.github.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant