From baf67c6aa87fdad0a968a1e111506ea0f4327be4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 07:49:35 +0000 Subject: [PATCH 1/7] CI: four job steps become one invocation, four in-process phases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator directive, 2026-08-20, on the merged #8618: "the regen steps seem to share a compile with all three steps - we basically need to consolidate ALL the work in there now - we added 2 more steps, but they are not properly managed (between github actions job steps) - i would much prefer if it was all handled within the witnesses step and within the gunbc binary, not at a github actions job level". WHAT THE STEP LADDER WAS. Four steps — parse, regen, regen-fixed-point, floor — each its own process; the ORDER a YAML list; each precondition an `if:` naming another step's `outcome`; and the fixed-point step receiving pass 1's digest by READING THE RECEIPT FILE the regen process had just written. That last one is the tell: `run_required_regen_fixed_point` has taken `pass1_digest: Option` all along and CI passed it `None`. A process boundary sat where a function call belonged. WHAT RUNS NOW. One step, `claim_executor --required-ci`, four phases in one process, the digest handed over in memory. ONLY ONE REAL DEPENDENCY EXISTS, and the rest is the behavioural change worth reading closely: fixed-point needs regen's pass-1 digest, so it is skipped — visibly, as its own reported state — when there is none. Every other phase RUNS EVEN AFTER AN EARLIER FAILURE, so the run reports the complete ledger instead of letting the first defect hide the rest. The line still stops (nonzero exit on any failed phase); it stops with every deficit named. Skipped is never silence and never a pass. The digest is handed over even when regen's comparison DISAGREED: pass 1 emitted a tree either way, and "does the emitter reproduce itself" is a separate question from "does it match what is committed". Skipping determinism on a regen mismatch would conflate them and lose the signal exactly when drift makes it interesting. NOT CLAIMED: no compile is shared. Regen and its fixed point each call `compile_stage0` and the second call STAYS — re-emitting and comparing digests is what the fixed point measures, so collapsing it would delete the measurement. The floor's preparation is a different computation again. What this removes is process startup, the receipt round-trip, and the job-level orchestration. ONE DEFECT I INTRODUCED AND CAUGHT, recorded because the shape matters more than the fix: extracting the parse walk from its bin, I dropped the `tests/fixtures/` exclusion. The first local run duly reported a parse FAILURE in `fact_cardinality_split_brace.dag` — a headerless fragment that is on main, where the parse step is green. The "finding" was my extraction having silently widened its own subject. Restored verbatim, and the subject is now provably identical to main's: 50 files parse-clean here, 50 in run 32341236470. One deliberate difference does remain, stated rather than smuggled: the bin used `read_dir.flatten()`, which silently DISCARDS an unreadable entry, so a walk that never saw a file was indistinguishable from a file that parsed. The error now propagates. SHARED, NOT DUPLICATED: `report_required_floor_outcome` and `required_floor_outcome_is_clean` are extracted so `--required-floor` and `--required-ci` cannot drift into reporting one outcome two ways, and the five-cause conjunction is written once (§3). STALE RECITALS UPDATED, because a knowingly-false present-tense claim in an authority is premise contamination: `gunbc.design_document` (twice), `gunbc.ci_layer_roots` `witness_fold_src_v1_coverage_gap_note`, and `tools.extdeps_scope_placement_gate`. Two other `--required-floor` mentions in `ci_layer_roots` are DATED MEASUREMENTS naming the command as run; they stay true and are untouched. EXECUTED: all four phases sequenced correctly in one local run — `first_generation_equal=true`, `fixed_point_equal=true` with no receipt round-trip, floor entered. Four new witnesses in `witness_floor_workflow_consolidation_witness_test.dag` assert one composed invocation, no cross-step outcome precondition, the parse sweep surviving, and the retired step NAMES not returning (a different axis from the commands, so the two can disagree). Mutation-tested: pointing the step back at `--required-floor` turns the first false. `cargo check --all-targets` and `cargo fmt --all --check` clean. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/witnesses.yml | 17 +- DESIGN.md | 2 +- dag/gunbc/ci_layer_roots.dag | 2 +- dag/gunbc/design_document.dag | 2 +- dag/gunbc/witness_floor_workflow.dag | 268 ++-------------- ...or_workflow_consolidation_witness_test.dag | 71 +++++ dag/tools/extdeps_scope_placement_gate.dag | 2 +- src/v1/stage0/src/bin/claim_executor.rs | 291 ++++++++++++++---- src/v1/stage0/src/bin/v1_src_dag_parse.rs | 120 ++------ src/v1/stage0/src/cli_run.rs | 127 ++++++++ 10 files changed, 477 insertions(+), 425 deletions(-) create mode 100644 dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 42aa005c09c..680b89550fb 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -31,22 +31,7 @@ jobs: id: build_witness_fold run: | cargo build --release -p v1-compiler --bin claim_executor --bin gunbc --bin v1_src_dag_parse - - name: src/v1 .dag sources parse - run: | - ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) - cd "$ROOT" && "$ROOT/target/release/v1_src_dag_parse" - - name: "Regen fixed point: first generation matches committed candidate" - id: required_regen - run: | - ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) - "$ROOT/target/release/claim_executor" --required-regen --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" - if: "!cancelled() && steps.build_witness_fold.outcome == 'success'" - - name: "Regen determinism: G0 emit reproduces itself on a second pass" - run: | - ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) - "$ROOT/target/release/claim_executor" --required-regen-fixed-point --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" - if: "!cancelled() && steps.required_regen.outcome == 'success'" - - name: All witnesses (one prepared subject, one fold) + - name: "Required CI: parse, regen, regen determinism, witness floor" run: | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) "$ROOT/target/release/claim_executor" --required-floor --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" diff --git a/DESIGN.md b/DESIGN.md index d777724ee43..a75de1e9114 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -144,4 +144,4 @@ hollow alias (minimality ≠ grounding) · state-space conflation (an `Option`/` - `cargo test --workspace` · `cargo clippy --all-targets -- -D warnings` · `cargo fmt --all --check` - one-time per clone: `git config core.hooksPath .githooks` — the only documented manual seed; generated pre-commit/pre-push hooks then idempotently converge `merge.generated-artifact.driver` and re-assert `core.hooksPath` via argv derived from `gunbc.repo_local_git_config` (clones that skip hooksPath degrade to vanilla text-merge for generated-artifact paths; drift gate still guards at CI). The driver REFUSES rather than answering `true`: git reaches a low-level merge driver only when both sides changed the path since the merge base — measured on a four-case matrix, one-sided and identical changes never reach it — and taking the ours side there dropped the other side's authority-derived bytes with no conflict, twice on #7836 against the stage0 seed. It now leaves the ours side in the worktree with no conflict markers, marks the path unmerged, and prints the regeneration recipe; the class is mechanically preventable, not structural, and its next-rung trigger is the commit-writer binding rows in `gunbc.commit_workflow` - explicit actuator (CI / tooling): `gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/repo_local_git_config.dag --function converge` -- CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one composed floor pass, `claim_executor` with a `--plan-entry` naming `src/v2/workflow/ci_floor_plan.dag`, a v2 scheduler deciding batches from `gunbc.ci_spec`, a compile-clean gate ordered ahead of everything else, per-PR discovery over `CiSpec.discovery_scan_dirs`, and a 4-hourly `affected-set-falsifier` cadence carrying the whole-tree cold controls. **ALL OF IT IS DELETED** — the workflows (`ci.yml`, `falsifier.yml`, `falsifier-alert.yml`), their `.dag` authorities, and ~30 witness modules. This paragraph previously recited that invocation in the present tense; it was false the moment the cut landed, and a knowingly-false recital in the canonical authority is premise contamination — every session reading it plans against a command that does not exist. WHAT RUNS NOW: one emission, `gunbc.witness_floor_workflow` → `.github/workflows/witnesses.yml`, invoking our own binary once — `claim_executor --required-floor --source-root dag --source-root src/v2` — which folds every discovered witness through one prepared subject via `v2.workflow.required_floor` `run_required_floor`. No plan entry, no plan function, no batch id, no worker role, no selection flag: the fold has no such concepts to configure — which PRESERVES the 2026-08-13 operator directive that the whole discovered roster runs unshrunk, by construction rather than by a flag someone must remember to set. That directive's own carrier (`corpus_selection_off_note`) died with the floor; `gunbc.ci_spec` retains the history and now points here. **THE FOLD NOW EXECUTES, measured 2026-08-19.** Three consecutive green runs on main, the latest `32203661157`: `planned=9008 executed=9008 terminal=9008 passed=8702 known_red_held=306 failed=0 stale_quarantine=0 budget_refused=0`, ~30 min wall. This clause previously read that no witness had executed and that the fold refused during preparation; that was true when written and is now false, and it is corrected in place rather than left standing until the rest of the paragraph can be rewritten — because a knowingly-false recital is premise contamination whichever direction it points, and this one had already cost real work: a session measuring the TestClaim orphan population read it, concluded the floor had never run, and raised a sequencing question about roster growth that does not exist. The rung drop below is unaffected: the fold running green establishes that the replacement executes, not that the re-add queue has closed. WHAT IS UNGUARDED IN THE MEANTIME, named rather than left to be rediscovered: the generated-artifact drift gates, heal, the seven effect gates, the fmt gate, merge-admission stamping, the falsifier cadence, and the per-witness eval deadline that `gunbc_ci_fast_lane_witness_eval_budget` used to arm (`gunbc.witness_row_cost` `gunbc_ci_fast_lane_rule_note` carries that one's own drop). Each is a separate re-add, each re-derived from its own first principles under its own operator agreement rather than restored from the deleted machinery. RESTORATION TRIGGER: this paragraph is rewritten as an ordinary present-tense description — not amended, rewritten in one pass — when the re-add queue closes. Until then it describes a rung drop, and describing the replacement as finished would be the inflation §4b names as worse than sitting low. **WHAT SURVIVES UNTOUCHED, enumerated rather than swept away with the bullet** — this paragraph replaced a longer one, and a prose row is deleted for one reason and takes everything in it unless its contents are enumerated first (the same rule that governs deleting a witness file whole): the compile-clean scope authority `tools.dag_compile_clean_scope` and its import-closure selection (`entry_file_touched_via_import_closure`) are live and unmodified by the cut — what is gone is the CI *job* that invoked them, not the authority; the `regen_input_sources` import closure survives and is now read by `v1_compiler.required_regen_host` — but the `regen_stage0` binary that consumed it, and the `RegenVerifyGate` / `SelfHostStalenessGate` pair that invoked it, are DELETED at the root (the regen cut), so the self-host fixed point is answered by `claim_executor --required-regen-fixed-point` and by nothing else. **THAT FLAG IS INVOKED AGAIN AS OF 2026-08-20**, by two `witnesses.yml` steps ordered ahead of the floor (`--required-regen`, then `--required-regen-fixed-point` guarded on the first succeeding), so this is one re-add off the queue below rather than a standing gap. It is recorded here with what made the gap real, because the steps were enrolled, STRIPPED, and re-enrolled inside twelve hours and a reader who finds only the enrolment will re-derive the strip. The strip was correct when made: the admission test is whether every red is closable by the author who caused it at the moment they caused it, and it was not -- the comparator normalized BOTH sides through rustfmt while writing the single-pass form, so after any candidate install the comparison was `normalize(normalize(emitted))` against `normalize(emitted)`, an identity only if rustfmt is idempotent. It is not, so the gate refused a tree byte-identical to its own artifact, permanently, and its only reachable green was the hand-edited mirror the gate exists to refuse -- a gate whose sole closing move is the forbidden action is not strict, it launders. Repairing it by raw-comparing the single-pass bytes then put it in direct contradiction with `cargo fmt --all --check`, which re-formats what is committed and so demands the NEXT pass: two gates consuming different passes of one artifact, each breaking the other. The resolution is that the emitted artifact is now written as a FIXED POINT of the formatter (bounded, exceeding the bound is a typed refusal), which makes the contradiction unrepresentable rather than detected and makes `cargo fmt` a no-op on it by definition -- the §5 construction move, and the general lesson is that any artifact with two consumers that normalize it must be stored in the normalizer's fixed point or the two consumers cannot both be satisfied. This clause previously read that the fixed point and its closure both survived with only their job removed; that was true when written and the regen cut falsified its first half, so it is rewritten here rather than annotated — a second sentence beside it would be two accounts of one fact; and parse remains grammar-owned — `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell or host parser — which was never a floor fact at all and is restated here so that dropping the bullet does not drop it. **TWO OPERATOR RULINGS ALSO LIVED IN THE REPLACED BULLET AND STILL HOLD**, restated because a ruling about what CI does *not* do is invisible once the paragraph describing CI is rewritten, and nothing in the new mechanism would contradict it loudly: the Rust test suite was removed from CI 2026-07-11 (operator ruling, recorded at `gunbc.commit_workflow` `commit_gate_rust_suite_removed_disposition`) and runs locally only; and clippy was removed from CI 2026-07-08, because a crate-wide `#![allow(clippy::all)]` made it zero-signal over ~44 minutes per run, leaving it a local dev check. Neither is reinstated by the replacement, and neither is the floor cut's to reverse. +- CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one composed floor pass, `claim_executor` with a `--plan-entry` naming `src/v2/workflow/ci_floor_plan.dag`, a v2 scheduler deciding batches from `gunbc.ci_spec`, a compile-clean gate ordered ahead of everything else, per-PR discovery over `CiSpec.discovery_scan_dirs`, and a 4-hourly `affected-set-falsifier` cadence carrying the whole-tree cold controls. **ALL OF IT IS DELETED** — the workflows (`ci.yml`, `falsifier.yml`, `falsifier-alert.yml`), their `.dag` authorities, and ~30 witness modules. This paragraph previously recited that invocation in the present tense; it was false the moment the cut landed, and a knowingly-false recital in the canonical authority is premise contamination — every session reading it plans against a command that does not exist. WHAT RUNS NOW: one emission, `gunbc.witness_floor_workflow` → `.github/workflows/witnesses.yml`, invoking our own binary once — `claim_executor --required-ci --source-root dag --source-root src/v2` (the invocation named `--required-floor` until the 2026-08-20 consolidation described below; that flag still exists and still runs the fold alone, it is simply no longer what CI calls) — whose floor phase folds every discovered witness through one prepared subject via `v2.workflow.required_floor` `run_required_floor`. No plan entry, no plan function, no batch id, no worker role, no selection flag: the fold has no such concepts to configure — which PRESERVES the 2026-08-13 operator directive that the whole discovered roster runs unshrunk, by construction rather than by a flag someone must remember to set. That directive's own carrier (`corpus_selection_off_note`) died with the floor; `gunbc.ci_spec` retains the history and now points here. **THE FOLD NOW EXECUTES, measured 2026-08-19.** Three consecutive green runs on main, the latest `32203661157`: `planned=9008 executed=9008 terminal=9008 passed=8702 known_red_held=306 failed=0 stale_quarantine=0 budget_refused=0`, ~30 min wall. This clause previously read that no witness had executed and that the fold refused during preparation; that was true when written and is now false, and it is corrected in place rather than left standing until the rest of the paragraph can be rewritten — because a knowingly-false recital is premise contamination whichever direction it points, and this one had already cost real work: a session measuring the TestClaim orphan population read it, concluded the floor had never run, and raised a sequencing question about roster growth that does not exist. The rung drop below is unaffected: the fold running green establishes that the replacement executes, not that the re-add queue has closed. WHAT IS UNGUARDED IN THE MEANTIME, named rather than left to be rediscovered: the generated-artifact drift gates, heal, the seven effect gates, the fmt gate, merge-admission stamping, the falsifier cadence, and the per-witness eval deadline that `gunbc_ci_fast_lane_witness_eval_budget` used to arm (`gunbc.witness_row_cost` `gunbc_ci_fast_lane_rule_note` carries that one's own drop). Each is a separate re-add, each re-derived from its own first principles under its own operator agreement rather than restored from the deleted machinery. RESTORATION TRIGGER: this paragraph is rewritten as an ordinary present-tense description — not amended, rewritten in one pass — when the re-add queue closes. Until then it describes a rung drop, and describing the replacement as finished would be the inflation §4b names as worse than sitting low. **WHAT SURVIVES UNTOUCHED, enumerated rather than swept away with the bullet** — this paragraph replaced a longer one, and a prose row is deleted for one reason and takes everything in it unless its contents are enumerated first (the same rule that governs deleting a witness file whole): the compile-clean scope authority `tools.dag_compile_clean_scope` and its import-closure selection (`entry_file_touched_via_import_closure`) are live and unmodified by the cut — what is gone is the CI *job* that invoked them, not the authority; the `regen_input_sources` import closure survives and is now read by `v1_compiler.required_regen_host` — but the `regen_stage0` binary that consumed it, and the `RegenVerifyGate` / `SelfHostStalenessGate` pair that invoked it, are DELETED at the root (the regen cut), so the self-host fixed point is answered by `claim_executor --required-regen-fixed-point` and by nothing else. **THAT FLAG IS INVOKED AGAIN AS OF 2026-08-20**, so this is one re-add off the queue below rather than a standing gap. It was re-added as two `witnesses.yml` steps ordered ahead of the floor and CONSOLIDATED the same day (operator directive: the phases belong `within the witnesses step and within the gunbc binary, not at a github actions job level`), so the invocation named at the top of this paragraph is superseded twice over and is now, exactly: `claim_executor --required-ci --source-root dag --source-root src/v2`, ONE step running four phases in ONE process — the src/v1 `.dag` parse sweep, `--required-regen`, its determinism pass receiving pass 1's digest IN MEMORY rather than by re-reading the receipt file, and the witness floor. Only the fixed-point phase has a real precondition; every other phase runs even after an earlier failure, so the run reports the complete ledger instead of letting the first defect hide the rest, and a phase that cannot run reports SKIPPED as its own state. The line still stops on any failed phase. What this did NOT buy, stated because the obvious reading is wrong: no compile is shared — regen and its fixed point each call `compile_stage0` and the second call is the measurement, not waste. It is recorded here with what made the gap real, because the steps were enrolled, STRIPPED, and re-enrolled inside twelve hours and a reader who finds only the enrolment will re-derive the strip. The strip was correct when made: the admission test is whether every red is closable by the author who caused it at the moment they caused it, and it was not -- the comparator normalized BOTH sides through rustfmt while writing the single-pass form, so after any candidate install the comparison was `normalize(normalize(emitted))` against `normalize(emitted)`, an identity only if rustfmt is idempotent. It is not, so the gate refused a tree byte-identical to its own artifact, permanently, and its only reachable green was the hand-edited mirror the gate exists to refuse -- a gate whose sole closing move is the forbidden action is not strict, it launders. Repairing it by raw-comparing the single-pass bytes then put it in direct contradiction with `cargo fmt --all --check`, which re-formats what is committed and so demands the NEXT pass: two gates consuming different passes of one artifact, each breaking the other. The resolution is that the emitted artifact is now written as a FIXED POINT of the formatter (bounded, exceeding the bound is a typed refusal), which makes the contradiction unrepresentable rather than detected and makes `cargo fmt` a no-op on it by definition -- the §5 construction move, and the general lesson is that any artifact with two consumers that normalize it must be stored in the normalizer's fixed point or the two consumers cannot both be satisfied. This clause previously read that the fixed point and its closure both survived with only their job removed; that was true when written and the regen cut falsified its first half, so it is rewritten here rather than annotated — a second sentence beside it would be two accounts of one fact; and parse remains grammar-owned — `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell or host parser — which was never a floor fact at all and is restated here so that dropping the bullet does not drop it. **TWO OPERATOR RULINGS ALSO LIVED IN THE REPLACED BULLET AND STILL HOLD**, restated because a ruling about what CI does *not* do is invisible once the paragraph describing CI is rewritten, and nothing in the new mechanism would contradict it loudly: the Rust test suite was removed from CI 2026-07-11 (operator ruling, recorded at `gunbc.commit_workflow` `commit_gate_rust_suite_removed_disposition`) and runs locally only; and clippy was removed from CI 2026-07-08, because a crate-wide `#![allow(clippy::all)]` made it zero-signal over ~44 minutes per run, leaving it a local dev check. Neither is reinstated by the replacement, and neither is the floor cut's to reverse. diff --git a/dag/gunbc/ci_layer_roots.dag b/dag/gunbc/ci_layer_roots.dag index f074255d56d..f68a116af81 100644 --- a/dag/gunbc/ci_layer_roots.dag +++ b/dag/gunbc/ci_layer_roots.dag @@ -907,7 +907,7 @@ fn bin_wet(entry: String, f: String) -> ScheduleWitnessEntry { data v1_claim_scoped_witness_batch_deleted_note: String = "THE SCOPED WITNESS BATCH IS DELETED (2026-08-15), and with it the only ScopedWitnessBatch instance that ever existed. It ran five file-grain v1 claim entries under a dag plus src/v1 source-root envelope, in a sequential child worker, so the v1 compiler closure could be resolved without widening witness_layer_roots for everything else.\n\nWHY IT COULD NOT SURVIVE AFFECTED-SET SELECTION. It was the last SelectionApplied consumer on the required path, and its 720-second clamp was derived (PR #8259) from a measured 565-second wall on the assumption that unrelated PRs skip it. With selection deleted the two available readings were both wrong: run it every PR and the required floor grows by roughly nine minutes under a clamp basis that no longer describes it, or keep a selection mechanism alive for one batch. Its cost was also never really the witnesses — the same receipt measured about 390 seconds of closure materialization before the first witness line and about 30 seconds of witness evaluation, so it was paying a whole second subject preparation to check thirty rows.\n\nWHAT THIS NARROWS, stated rather than implied: the five entries under src/v1/tests/claim have NO executing consumer. They are not covered by ordinary discovery, whose roots are dag plus src/v2. Their generated stage0 emissions still compile into the seed crate, which is not execution. This is a real loss of coverage over v1 parser and namespace-binding behavior, taken deliberately as part of deleting the floor rather than discovered later, and it is NOT backstopped by any cadence — the cadence lane those rows might have moved to was refused for the same reason the per-PR slot was: a second subject envelope is the defect, not the schedule it runs on.\n\nRESTORATION: one prepared subject spanning dag, src/v1 and src/v2, at which point a separate envelope, a separate worker, a separate clamp and a separate receipt family all stop existing and these rows are ordinary discovery." -data witness_fold_src_v1_coverage_gap_note: String = "DECLARED COVERAGE GAP (operator ruling relayed 2026-08-15, at the re-add #1 boundary). The required witness fold runs `claim_executor --required-floor --source-root dag --source-root src/v2`, so every witness under src/v1 is OUTSIDE it. Measured at declaration: 116 `test fn` declarations across 15 files under src/v1/tests/claim. Their previous consumer was the scoped witness batch deleted above; nothing has replaced it, and no cadence backstops them.\n\nWHY THE ONE-TOKEN FIX WAS REFUSED. Adding --source-root src/v1 would also admit the 52 non-test .dag modules under src/v1 into the ONE shared prepared subject the fold depends on. That subject is a single flat namespace per scope, and the v1 tree declares its own module population; poisoning the fold's first census with collisions to chase coverage costs more than the gap, and a first measurement taken against a subject nobody has run before is not a measurement worth having.\n\nWHY IT IS DECLARED RATHER THAN QUIET. run_required_floor refuses internally when planned, executed and terminal identity counts disagree, precisely so a narrowed roster cannot present as a roster. That wall operates INSIDE the subject; it cannot see a population the subject never admitted. So the same honesty has to be carried here, in prose, at the boundary: the fold runs every witness IN ITS SUBJECT, and its subject is two roots, not three.\n\nPOPULATION LOCATION IS BRANCH-DEPENDENT (relayed 2026-08-15, after this row was authored). The 15 files are present on main and DELETED on integration/v1-cut, where a src/v1/**/*.dag root deletion swept 16 witness .dag files under src/v1/tests/claim — this population — as collateral. So a reader checking main and a reader checking that cut branch will disagree about whether the gap has a subject at all, and the dissolve-on below is only satisfiable on a branch where the files still exist. A per-file disposition proposal (re-home under a surviving subject, or retire with a receipt naming the rejected behavior) is routed elsewhere and is deliberately NOT acted on here; several of the 16 witness LANGUAGE behavior rather than v1-compiler behavior (§4c annotation capture and erasure, the line-continuation operator-table derivation), so a blanket retire would drop evidence for obligations the replacement still claims. This row stays a declared gap until that proposal lands, at which point it either dissolves for real (files re-homed and inside the fold) or converts to a retirement receipt.\n\nDISSOLVE-ON: the boundary decision on these 15 files. The recommendation carried into that decision is RELOCATION into the dag test root rather than widening the root set — one tree, one namespace, consistent with every cut currently consolidating away from src/v1 — with the caveat that three sibling cuts are reshaping src/v1 concurrently and some of these 116 may die with them, so the relocation census is taken at the boundary and not before it." +data witness_fold_src_v1_coverage_gap_note: String = "DECLARED COVERAGE GAP (operator ruling relayed 2026-08-15, at the re-add #1 boundary). The required witness fold runs `claim_executor --required-ci --source-root dag --source-root src/v2` (the flag was `--required-floor` until the 2026-08-20 consolidation folded parse, regen and the floor into one invocation; the source roots are unchanged, so this gap is unchanged too), so every witness under src/v1 is OUTSIDE it. Measured at declaration: 116 `test fn` declarations across 15 files under src/v1/tests/claim. Their previous consumer was the scoped witness batch deleted above; nothing has replaced it, and no cadence backstops them.\n\nWHY THE ONE-TOKEN FIX WAS REFUSED. Adding --source-root src/v1 would also admit the 52 non-test .dag modules under src/v1 into the ONE shared prepared subject the fold depends on. That subject is a single flat namespace per scope, and the v1 tree declares its own module population; poisoning the fold's first census with collisions to chase coverage costs more than the gap, and a first measurement taken against a subject nobody has run before is not a measurement worth having.\n\nWHY IT IS DECLARED RATHER THAN QUIET. run_required_floor refuses internally when planned, executed and terminal identity counts disagree, precisely so a narrowed roster cannot present as a roster. That wall operates INSIDE the subject; it cannot see a population the subject never admitted. So the same honesty has to be carried here, in prose, at the boundary: the fold runs every witness IN ITS SUBJECT, and its subject is two roots, not three.\n\nPOPULATION LOCATION IS BRANCH-DEPENDENT (relayed 2026-08-15, after this row was authored). The 15 files are present on main and DELETED on integration/v1-cut, where a src/v1/**/*.dag root deletion swept 16 witness .dag files under src/v1/tests/claim — this population — as collateral. So a reader checking main and a reader checking that cut branch will disagree about whether the gap has a subject at all, and the dissolve-on below is only satisfiable on a branch where the files still exist. A per-file disposition proposal (re-home under a surviving subject, or retire with a receipt naming the rejected behavior) is routed elsewhere and is deliberately NOT acted on here; several of the 16 witness LANGUAGE behavior rather than v1-compiler behavior (§4c annotation capture and erasure, the line-continuation operator-table derivation), so a blanket retire would drop evidence for obligations the replacement still claims. This row stays a declared gap until that proposal lands, at which point it either dissolves for real (files re-homed and inside the fold) or converts to a retirement receipt.\n\nDISSOLVE-ON: the boundary decision on these 15 files. The recommendation carried into that decision is RELOCATION into the dag test root rather than widening the root set — one tree, one namespace, consistent with every cut currently consolidating away from src/v1 — with the caveat that three sibling cuts are reshaping src/v1 concurrently and some of these 116 may die with them, so the relocation census is taken at the boundary and not before it." data v1_dead_witness_tree_triage_receipt: String = "DISCHARGES witness_fold_src_v1_coverage_gap_note's routed-elsewhere per-file disposition proposal (dashboard node adhoc-9b80ec49-d63, session zesty-newt-828, 2026-08-18/19). COUNT CORRECTION: that note measured 116 test fn declarations across 15 files at 2026-08-15 declaration time; by the time this triage read the tree the population was 17 files (12 disposed directly in this receipt, plus 5 files — checkpoint_identity_keying_witness_test.dag, occurrence_binding_parser_walk_witness_test.dag, occurrence_identity_debt_receipt_test.dag, pattern_binder_declaration_node_test.dag, v1_match_pattern_identity_test.dag — dispositioned by a concurrent batch and not re-narrated here; ATTRIBUTION, confirmed against git show --stat 11113ac9e9: all 17 files, including these 5, were deleted in that ONE commit, one author (Brian Searls), on this same branch session/zesty-newt-828 — there is no separate sibling dashboard session to attribute for PR purposes, 'concurrent' here means a parallel work stream inside this session, not a different session's contribution; checkpoint_identity_keying_witness_test.dag alone is carved out to stern-fox-619's own PR per the binding scope exclusion, unaffected by this attribution). The 15-vs-17 gap is branch drift between 2026-08-15 and the triage read, not a miscount of either measurement.\n\nDISPOSITION, MIGRATED (relocated to dag/test/claim, rewritten onto compile_dag_rust_emit_check, running under ordinary discovery — source roots dag + src/v2, no v1 import): dag_parse_continuation_operator_witness_test.dag -> dag/test/claim/dag_parse_continuation_operator_witness_test.dag (8 of 9 tests migrated black-box; w_dual_role_exclusion_intersection_is_singleton_minus retired in-file, pure operator-table introspection with no compile-observable surface, its protected family covered black-box by the comparison-family test beside it). required_expr_newline_continuation_test.dag -> dag/test/claim/required_expr_newline_continuation_witness_test.dag (all 8 tests migrated 1:1, 5 parse-clean + 3 refuses-to-compile each paired against a proven-clean sibling). ordinary_frontend_observation_test.dag -> dag/test/claim/frontend_literal_and_compile_witness_test.dag (2 pure interpreter-literal tests moved unchanged, no v1 import ever needed; 2 compile-observation tests converted to compile_dag_rust_emit_check clean/broken-subject pairs). caret_parse_smoke_test.dag -> dag/test/claim/caret_syntax_witness_test.dag (3 black-box compile/emit-substring tests migrated; 7 white-box tokenizer/parser-internals tests retired in-file, no compile-observable equivalent). v1_annotation_target_emission_test.dag -> dag/test/claim/annotation_erasure_emission_witness_test.dag (the D-C property 4 erasure claim plus a non-vacuity control, both via compile_dag_rust_emit_check includes/excludes on emitted Rust text; not a byte-identical replay of the 2026-08-05 host-execution diff receipt, which stands as separate historical evidence).\n\nDISPOSITION, RETIRED (white-box, no compile-observable proxy for the same claim; deleted with this receipt as the record): v1_annotation_binding_test.dag, v1_annotation_capture_test.dag, v1_annotation_round_trip_test.dag, v1_annotation_erasure_test.dag — all four inspect v1-internal occurrence-identity/annotation-graph/span-provenance/node-fingerprint state directly (std.occurrence_identity, v1.compiler.annotation_bind, dag_node_surface_fingerprint) that has no representation in emitted Rust text; per DESIGN §4c, annotation capture/erasure is disjoint from semantic occurrence identity by construction, so these are facts about the FRONTEND's internal bookkeeping, not about a compiled program, and cannot be reduced to a compile_dag_rust_emit_check assertion without inventing a second v1-internal-state-reading surface this triage was not chartered to build. v1_annotation_erasure_test.dag's own erasure claim (prose does not leak into emitted output) is separately and already covered black-box by the migrated annotation_erasure_emission_witness_test.dag above — that file migrates v1_annotation_target_emission_test.dag, a distinct source, not this one; naming this precisely to avoid the false impression that this exact file was migrated rather than retired. v1_complexity_eviction_hazard_test.dag — retired with a live-defect note, not a clean retirement, structured as an explicit DESIGN §4b(3) declared bounded rung-drop rather than left as prose implying it: it documents a real fail-open in v1.compiler.complexity's evict_summary/lookup_summary (a §5 absorbing-fallback shape) that is orthogonal to emitted-Rust text and therefore not compile-observable, and the defect itself remains live and unfixed by this retirement. PREVIOUS RUNG: mechanically preventable — before v1_claim_scoped_witness_batch was deleted (2026-08-15), this file's assertions executed and would have caught a regression in the eviction fail-open shape. TEMPORARY RUNG: mitigatable — the fail-open itself is unrepaired, so this class sits at the floor rung DESIGN §5 names for an absorbing fallback (a typed, located, counted diagnostic is owed but not yet built); this retirement removes only the dead witness, it does not climb or lower the underlying defect's own rung. REASON: the witness's sole execution path (v1_claim_scoped_witness_batch) is gone and RESTORE (widening the required floor to admit src/v1) was measured and ruled out repo-wide in this same triage. RE-MEASURED 2026-08-19 (gunbc#8520, CI run 32219326621): the stall does NOT reproduce -- preparation completes in ~9 minutes and refuses with 532 typed located diagnostics, none of them in src/v1. The ruling stands on a different cause (twelve last-segment module collisions between src/v1 and src/v2 breaking bare cross-module references that resolve only by pool coincidence), and the trigger is namespace-only resolution rather than any floor speedup. The single corrected measurement lives in v1_dead_witness_tree_triage_receipt_remainder; this row cites it rather than restating it. RESTORATION TRIGGER: a v1-compiler-local cargo test (v1 is semantics-frozen with active maintenance per gunbc.v1_maintenance_standing, and a local Rust-side regression test is an admitted maintenance-class change under that standing, unlike widening the required floor's source roots) — or, separately and preferably, root-causing the fail-open itself into a typed refusal per §5, which would obsolete this witness's assertions rather than merely restore them. namespace_reference_derived_closure_production_admissions_witness_test.dag — retired: white-box, matched ReferenceBindingObservation variant shapes directly rather than compile outcomes; its own fixtures were already documented in-file as illustrative-only and not satisfying the namespace-structural-observations closing contract (see below).\n\nOUT OF SCOPE, NOT A WITNESS, LEFT IN PLACE: src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag is a v1 PRODUCTION module (four ReferenceBindingObservation producers for the namespace-reference-derived-closure clauses a-d), not a test file, so it is outside this triage's 17-file population. Its only historical test consumer was the retired namespace_reference_derived_closure_production_admissions_witness_test.dag above, so it currently has zero executing consumers and zero test coverage — but it is not dead code: gunbc.roadmap_authority's namespace_reference_derived_closure_execution_contract (an open WorkItemExecutionContract, enrolled QuarantineProbeExpectRed) names 'the gated node' supplying production observations for clauses a-d as the precondition for that contract to green, and this module's namespace_structural_binding_observations_a_through_d export is the designated future producer for exactly that seam. Retiring it would delete a still-claimed future authority, not dead scaffolding; it is left in place with this receipt recording its coverage gap rather than silently carrying it.\n\nRECEIPT COMPLETENESS: undispositioned files = 0 among the 12 handled directly here; retired files' evidence is deleted with them (no discriminating claim survives that a compile-observable check could re-prove); migrated files' evidence is the new dag/test/claim files enumerated above, executing under ordinary per-PR discovery. witness_fold_src_v1_coverage_gap_note's dissolve-on ('the boundary decision on these 15 files... RELOCATION into the dag test root rather than widening the root set') is satisfied by this receipt for the population it could reach; that note is retained rather than deleted because it is still the correct historical account of why the gap existed and how the fold's subject is bounded, and because the 5-file concurrent-batch population is disposed under a separate session-local record this row does not restate." diff --git a/dag/gunbc/design_document.dag b/dag/gunbc/design_document.dag index 07be36b0cee..e2fe0bbc9ff 100644 --- a/dag/gunbc/design_document.dag +++ b/dag/gunbc/design_document.dag @@ -179,7 +179,7 @@ fn building_checks_blocks() -> List { li(text: "`cargo test --workspace` · `cargo clippy --all-targets -- -D warnings` · `cargo fmt --all --check`"), li(text: "one-time per clone: `git config core.hooksPath .githooks` — the only documented manual seed; generated pre-commit/pre-push hooks then idempotently converge `merge.generated-artifact.driver` and re-assert `core.hooksPath` via argv derived from `gunbc.repo_local_git_config` (clones that skip hooksPath degrade to vanilla text-merge for generated-artifact paths; drift gate still guards at CI). The driver REFUSES rather than answering `true`: git reaches a low-level merge driver only when both sides changed the path since the merge base — measured on a four-case matrix, one-sided and identical changes never reach it — and taking the ours side there dropped the other side's authority-derived bytes with no conflict, twice on #7836 against the stage0 seed. It now leaves the ours side in the worktree with no conflict markers, marks the path unmerged, and prints the regeneration recipe; the class is mechanically preventable, not structural, and its next-rung trigger is the commit-writer binding rows in `gunbc.commit_workflow`"), li(text: "explicit actuator (CI / tooling): `gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/repo_local_git_config.dag --function converge`"), - li(text: "CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one composed floor pass, `claim_executor` with a `--plan-entry` naming `src/v2/workflow/ci_floor_plan.dag`, a v2 scheduler deciding batches from `gunbc.ci_spec`, a compile-clean gate ordered ahead of everything else, per-PR discovery over `CiSpec.discovery_scan_dirs`, and a 4-hourly `affected-set-falsifier` cadence carrying the whole-tree cold controls. **ALL OF IT IS DELETED** — the workflows (`ci.yml`, `falsifier.yml`, `falsifier-alert.yml`), their `.dag` authorities, and ~30 witness modules. This paragraph previously recited that invocation in the present tense; it was false the moment the cut landed, and a knowingly-false recital in the canonical authority is premise contamination — every session reading it plans against a command that does not exist. WHAT RUNS NOW: one emission, `gunbc.witness_floor_workflow` → `.github/workflows/witnesses.yml`, invoking our own binary once — `claim_executor --required-floor --source-root dag --source-root src/v2` — which folds every discovered witness through one prepared subject via `v2.workflow.required_floor` `run_required_floor`. No plan entry, no plan function, no batch id, no worker role, no selection flag: the fold has no such concepts to configure — which PRESERVES the 2026-08-13 operator directive that the whole discovered roster runs unshrunk, by construction rather than by a flag someone must remember to set. That directive's own carrier (`corpus_selection_off_note`) died with the floor; `gunbc.ci_spec` retains the history and now points here. **THE FOLD NOW EXECUTES, measured 2026-08-19.** Three consecutive green runs on main, the latest `32203661157`: `planned=9008 executed=9008 terminal=9008 passed=8702 known_red_held=306 failed=0 stale_quarantine=0 budget_refused=0`, ~30 min wall. This clause previously read that no witness had executed and that the fold refused during preparation; that was true when written and is now false, and it is corrected in place rather than left standing until the rest of the paragraph can be rewritten — because a knowingly-false recital is premise contamination whichever direction it points, and this one had already cost real work: a session measuring the TestClaim orphan population read it, concluded the floor had never run, and raised a sequencing question about roster growth that does not exist. The rung drop below is unaffected: the fold running green establishes that the replacement executes, not that the re-add queue has closed. WHAT IS UNGUARDED IN THE MEANTIME, named rather than left to be rediscovered: the generated-artifact drift gates, heal, the seven effect gates, the fmt gate, merge-admission stamping, the falsifier cadence, and the per-witness eval deadline that `gunbc_ci_fast_lane_witness_eval_budget` used to arm (`gunbc.witness_row_cost` `gunbc_ci_fast_lane_rule_note` carries that one's own drop). Each is a separate re-add, each re-derived from its own first principles under its own operator agreement rather than restored from the deleted machinery. RESTORATION TRIGGER: this paragraph is rewritten as an ordinary present-tense description — not amended, rewritten in one pass — when the re-add queue closes. Until then it describes a rung drop, and describing the replacement as finished would be the inflation §4b names as worse than sitting low. **WHAT SURVIVES UNTOUCHED, enumerated rather than swept away with the bullet** — this paragraph replaced a longer one, and a prose row is deleted for one reason and takes everything in it unless its contents are enumerated first (the same rule that governs deleting a witness file whole): the compile-clean scope authority `tools.dag_compile_clean_scope` and its import-closure selection (`entry_file_touched_via_import_closure`) are live and unmodified by the cut — what is gone is the CI *job* that invoked them, not the authority; the `regen_input_sources` import closure survives and is now read by `v1_compiler.required_regen_host` — but the `regen_stage0` binary that consumed it, and the `RegenVerifyGate` / `SelfHostStalenessGate` pair that invoked it, are DELETED at the root (the regen cut), so the self-host fixed point is answered by `claim_executor --required-regen-fixed-point` and by nothing else. **THAT FLAG IS INVOKED AGAIN AS OF 2026-08-20**, by two `witnesses.yml` steps ordered ahead of the floor (`--required-regen`, then `--required-regen-fixed-point` guarded on the first succeeding), so this is one re-add off the queue below rather than a standing gap. It is recorded here with what made the gap real, because the steps were enrolled, STRIPPED, and re-enrolled inside twelve hours and a reader who finds only the enrolment will re-derive the strip. The strip was correct when made: the admission test is whether every red is closable by the author who caused it at the moment they caused it, and it was not -- the comparator normalized BOTH sides through rustfmt while writing the single-pass form, so after any candidate install the comparison was `normalize(normalize(emitted))` against `normalize(emitted)`, an identity only if rustfmt is idempotent. It is not, so the gate refused a tree byte-identical to its own artifact, permanently, and its only reachable green was the hand-edited mirror the gate exists to refuse -- a gate whose sole closing move is the forbidden action is not strict, it launders. Repairing it by raw-comparing the single-pass bytes then put it in direct contradiction with `cargo fmt --all --check`, which re-formats what is committed and so demands the NEXT pass: two gates consuming different passes of one artifact, each breaking the other. The resolution is that the emitted artifact is now written as a FIXED POINT of the formatter (bounded, exceeding the bound is a typed refusal), which makes the contradiction unrepresentable rather than detected and makes `cargo fmt` a no-op on it by definition -- the §5 construction move, and the general lesson is that any artifact with two consumers that normalize it must be stored in the normalizer's fixed point or the two consumers cannot both be satisfied. This clause previously read that the fixed point and its closure both survived with only their job removed; that was true when written and the regen cut falsified its first half, so it is rewritten here rather than annotated — a second sentence beside it would be two accounts of one fact; and parse remains grammar-owned — `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell or host parser — which was never a floor fact at all and is restated here so that dropping the bullet does not drop it. **TWO OPERATOR RULINGS ALSO LIVED IN THE REPLACED BULLET AND STILL HOLD**, restated because a ruling about what CI does *not* do is invisible once the paragraph describing CI is rewritten, and nothing in the new mechanism would contradict it loudly: the Rust test suite was removed from CI 2026-07-11 (operator ruling, recorded at `gunbc.commit_workflow` `commit_gate_rust_suite_removed_disposition`) and runs locally only; and clippy was removed from CI 2026-07-08, because a crate-wide `#![allow(clippy::all)]` made it zero-signal over ~44 minutes per run, leaving it a local dev check. Neither is reinstated by the replacement, and neither is the floor cut's to reverse."), + li(text: "CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one composed floor pass, `claim_executor` with a `--plan-entry` naming `src/v2/workflow/ci_floor_plan.dag`, a v2 scheduler deciding batches from `gunbc.ci_spec`, a compile-clean gate ordered ahead of everything else, per-PR discovery over `CiSpec.discovery_scan_dirs`, and a 4-hourly `affected-set-falsifier` cadence carrying the whole-tree cold controls. **ALL OF IT IS DELETED** — the workflows (`ci.yml`, `falsifier.yml`, `falsifier-alert.yml`), their `.dag` authorities, and ~30 witness modules. This paragraph previously recited that invocation in the present tense; it was false the moment the cut landed, and a knowingly-false recital in the canonical authority is premise contamination — every session reading it plans against a command that does not exist. WHAT RUNS NOW: one emission, `gunbc.witness_floor_workflow` → `.github/workflows/witnesses.yml`, invoking our own binary once — `claim_executor --required-ci --source-root dag --source-root src/v2` (the invocation named `--required-floor` until the 2026-08-20 consolidation described below; that flag still exists and still runs the fold alone, it is simply no longer what CI calls) — whose floor phase folds every discovered witness through one prepared subject via `v2.workflow.required_floor` `run_required_floor`. No plan entry, no plan function, no batch id, no worker role, no selection flag: the fold has no such concepts to configure — which PRESERVES the 2026-08-13 operator directive that the whole discovered roster runs unshrunk, by construction rather than by a flag someone must remember to set. That directive's own carrier (`corpus_selection_off_note`) died with the floor; `gunbc.ci_spec` retains the history and now points here. **THE FOLD NOW EXECUTES, measured 2026-08-19.** Three consecutive green runs on main, the latest `32203661157`: `planned=9008 executed=9008 terminal=9008 passed=8702 known_red_held=306 failed=0 stale_quarantine=0 budget_refused=0`, ~30 min wall. This clause previously read that no witness had executed and that the fold refused during preparation; that was true when written and is now false, and it is corrected in place rather than left standing until the rest of the paragraph can be rewritten — because a knowingly-false recital is premise contamination whichever direction it points, and this one had already cost real work: a session measuring the TestClaim orphan population read it, concluded the floor had never run, and raised a sequencing question about roster growth that does not exist. The rung drop below is unaffected: the fold running green establishes that the replacement executes, not that the re-add queue has closed. WHAT IS UNGUARDED IN THE MEANTIME, named rather than left to be rediscovered: the generated-artifact drift gates, heal, the seven effect gates, the fmt gate, merge-admission stamping, the falsifier cadence, and the per-witness eval deadline that `gunbc_ci_fast_lane_witness_eval_budget` used to arm (`gunbc.witness_row_cost` `gunbc_ci_fast_lane_rule_note` carries that one's own drop). Each is a separate re-add, each re-derived from its own first principles under its own operator agreement rather than restored from the deleted machinery. RESTORATION TRIGGER: this paragraph is rewritten as an ordinary present-tense description — not amended, rewritten in one pass — when the re-add queue closes. Until then it describes a rung drop, and describing the replacement as finished would be the inflation §4b names as worse than sitting low. **WHAT SURVIVES UNTOUCHED, enumerated rather than swept away with the bullet** — this paragraph replaced a longer one, and a prose row is deleted for one reason and takes everything in it unless its contents are enumerated first (the same rule that governs deleting a witness file whole): the compile-clean scope authority `tools.dag_compile_clean_scope` and its import-closure selection (`entry_file_touched_via_import_closure`) are live and unmodified by the cut — what is gone is the CI *job* that invoked them, not the authority; the `regen_input_sources` import closure survives and is now read by `v1_compiler.required_regen_host` — but the `regen_stage0` binary that consumed it, and the `RegenVerifyGate` / `SelfHostStalenessGate` pair that invoked it, are DELETED at the root (the regen cut), so the self-host fixed point is answered by `claim_executor --required-regen-fixed-point` and by nothing else. **THAT FLAG IS INVOKED AGAIN AS OF 2026-08-20**, so this is one re-add off the queue below rather than a standing gap. It was re-added as two `witnesses.yml` steps ordered ahead of the floor and CONSOLIDATED the same day (operator directive: the phases belong `within the witnesses step and within the gunbc binary, not at a github actions job level`), so the invocation named at the top of this paragraph is superseded twice over and is now, exactly: `claim_executor --required-ci --source-root dag --source-root src/v2`, ONE step running four phases in ONE process — the src/v1 `.dag` parse sweep, `--required-regen`, its determinism pass receiving pass 1's digest IN MEMORY rather than by re-reading the receipt file, and the witness floor. Only the fixed-point phase has a real precondition; every other phase runs even after an earlier failure, so the run reports the complete ledger instead of letting the first defect hide the rest, and a phase that cannot run reports SKIPPED as its own state. The line still stops on any failed phase. What this did NOT buy, stated because the obvious reading is wrong: no compile is shared — regen and its fixed point each call `compile_stage0` and the second call is the measurement, not waste. It is recorded here with what made the gap real, because the steps were enrolled, STRIPPED, and re-enrolled inside twelve hours and a reader who finds only the enrolment will re-derive the strip. The strip was correct when made: the admission test is whether every red is closable by the author who caused it at the moment they caused it, and it was not -- the comparator normalized BOTH sides through rustfmt while writing the single-pass form, so after any candidate install the comparison was `normalize(normalize(emitted))` against `normalize(emitted)`, an identity only if rustfmt is idempotent. It is not, so the gate refused a tree byte-identical to its own artifact, permanently, and its only reachable green was the hand-edited mirror the gate exists to refuse -- a gate whose sole closing move is the forbidden action is not strict, it launders. Repairing it by raw-comparing the single-pass bytes then put it in direct contradiction with `cargo fmt --all --check`, which re-formats what is committed and so demands the NEXT pass: two gates consuming different passes of one artifact, each breaking the other. The resolution is that the emitted artifact is now written as a FIXED POINT of the formatter (bounded, exceeding the bound is a typed refusal), which makes the contradiction unrepresentable rather than detected and makes `cargo fmt` a no-op on it by definition -- the §5 construction move, and the general lesson is that any artifact with two consumers that normalize it must be stored in the normalizer's fixed point or the two consumers cannot both be satisfied. This clause previously read that the fixed point and its closure both survived with only their job removed; that was true when written and the regen cut falsified its first half, so it is rewritten here rather than annotated — a second sentence beside it would be two accounts of one fact; and parse remains grammar-owned — `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell or host parser — which was never a floor fact at all and is restated here so that dropping the bullet does not drop it. **TWO OPERATOR RULINGS ALSO LIVED IN THE REPLACED BULLET AND STILL HOLD**, restated because a ruling about what CI does *not* do is invisible once the paragraph describing CI is rewritten, and nothing in the new mechanism would contradict it loudly: the Rust test suite was removed from CI 2026-07-11 (operator ruling, recorded at `gunbc.commit_workflow` `commit_gate_rust_suite_removed_disposition`) and runs locally only; and clippy was removed from CI 2026-07-08, because a crate-wide `#![allow(clippy::all)]` made it zero-signal over ~44 minutes per run, leaving it a local dev check. Neither is reinstated by the replacement, and neither is the floor cut's to reverse."), ]), ] } diff --git a/dag/gunbc/witness_floor_workflow.dag b/dag/gunbc/witness_floor_workflow.dag index e4bb26d614a..731a4081e7b 100644 --- a/dag/gunbc/witness_floor_workflow.dag +++ b/dag/gunbc/witness_floor_workflow.dag @@ -210,6 +210,27 @@ fn witness_floor_source_root_flags() -> String { // decided in .dag by v2.workflow.required_floor. This step passes the source roots and // nothing else: there is no plan entry, no plan function, no batch id, no worker role and no // selection flag, because the fold has no such concepts to configure. +// ONE INVOCATION, FOUR PHASES, AND THE ORDER IS THE PROGRAM'S RATHER THAN THE JOB'S +// (operator directive, 2026-08-20: "i would much prefer if it was all handled within the +// witnesses step and within the gunbc binary, not at a github actions job level"). +// +// WHAT THIS REPLACES: four steps -- parse, regen, regen-fixed-point, floor -- each its own +// process, their ORDER a YAML list, and each one's precondition an `if:` naming another +// step's `outcome`. The fixed-point step additionally received pass 1's digest by READING +// THE RECEIPT FILE the regen process had just written, which is the tell that a process +// boundary sat where a function call belonged: `run_required_regen_fixed_point` has taken +// `pass1_digest: Option` all along and CI passed it `None`. +// +// `--required-ci` runs all four in one process, hands the digest over in memory, and reports +// the complete ledger: every phase but the fixed point is independent, so an earlier failure +// no longer skips a later phase and one defect can no longer hide the others. The line still +// stops -- nonzero exit on any failed phase -- and a phase that could not run reports SKIPPED +// as its own state, never as silence and never as a pass. +// +// NOT CLAIMED: this shares no COMPILE. Regen and its fixed point each call `compile_stage0` +// and the second call stays, because re-emitting and comparing digests is exactly what the +// fixed point measures. What is removed is process startup, the receipt round-trip, and the +// job-level orchestration. fn witness_floor_run_script() -> String { concat( concat("ROOT=", concat(ci_repo_root_shell(), "\n")), @@ -220,63 +241,6 @@ fn witness_floor_run_script() -> String { ) } -// src/v1's .dag SOURCES HAVE NO REQUIRED GATE, AND THE REASON IS STRUCTURAL RATHER THAN AN -// OVERSIGHT. Two things were tried before this step, and both were measured wrong. -// -// (1) `--source-root src/v1` on the fold refuses. Measured, CI run 32219326621: one flat -// namespace over both trees yields 12 last-segment collisions -- `tokenize`, `parse`, `resolve`, -// `emit`, `infer`, `normalize` and six more -- because the v1 and v2 compilers name their stages -// identically. All 532 diagnostics that run produced were in src/v2 and dag/test; ZERO in src/v1. -// The blocker is bare cross-module reference binding, and it dissolves with namespace-only -// resolution rather than with a flag. -// -// (2) `test.claim.v1_dag_parse_witness.v1_dag_parse_witnesses` EXISTS and is discovered by the -// fold, which looks like the gate already being present. It is not, and the reason is the one -// `gunbc.ci_layer_roots` `excl_bin_wet_reason` states: this fold is HERMETIC, the witness reaches -// `shell.Test.IsExecutable` through `tools.host_prelude`, and the hermetic envelope refuses host -// effects by construction. Measured, CI run 32223611085: -// -// FAIL test.claim.v1_dag_parse_witness.v1_dag_parse_witnesses errored: -// type error: hermetic mode: no mock_response for operation IsExecutable -// -- refusing to fabricate Unit -// -// That refusal is CORRECT and the witness cannot be made hermetic -- its subject is a subprocess -// exit status, which no in-process assertion can observe, and mocking it would let the witness -// pass against a fabricated status. So the row stays on `floor_expected_red`. What is actually -// missing is its executing consumer: `bin_witness_wet_entries` was the declared per-PR wet batch -// for this whole class, and it died with the old floor. The class has had no executor since. -// -// THIS STEP IS THAT EXECUTOR, for one member of the class, and it runs OUTSIDE the fold because -// outside the fold is the only place a host effect can happen. It is not a second authority over -// the witness: the witness cannot execute, so there is no verdict for this to duplicate. -// -// WHAT IT CLAIMS. Each src/v1 .dag file parses INDEPENDENTLY -- no shared pool, no cross-module -// resolution -- so (1)'s collision class never arises. One rung: a parse error cannot land -// silently. NOTHING about resolution, types, or whether those modules mean anything. -// `tests/fixtures/` is excluded; those are deliberately malformed inputs authored FOR the -// parser's own tests, and the existing corpus discovery in `compiler_tests` excludes them on the -// same grounds. -// -// THE BINARY IS A REGISTERED SCAFFOLD AND STAYS ONE. Its dissolve-on is the modeled pipeline -// parsing src/v1; wiring the scaffold does not satisfy that. Ordered BEFORE the fold because it -// costs 0.7 seconds against the fold's tens of minutes, and a stopped line is cheapest early. -fn witness_floor_v1_parse_step() -> Step { - RunStep { - name: Present { value: "src/v1 .dag sources parse" }, - id: none, - run: concat( - concat("ROOT=", concat(ci_repo_root_shell(), "\n")), - "cd \"$ROOT\" && \"$ROOT/target/release/v1_src_dag_parse\"\n" - ), - shell: none, - env: none, - working_directory: none, - if_condition: none, - continue_on_error: none, - timeout_minutes: none - } -} - // THE FLOOR RUNS LAST AND THEREFORE DECLARES ITS PRECONDITION TOO (operator directive, // 2026-08-20: "move regen before witnesses"). Regen costs ~4 minutes and the floor ~30, so // ordering the cheap total check first turns a drift report from a 35-minute wait into a @@ -329,7 +293,7 @@ fn witness_floor_precondition() -> String { fn witness_floor_run_step() -> Step { RunStep { - name: Present { value: "All witnesses (one prepared subject, one fold)" }, + name: Present { value: "Required CI: parse, regen, regen determinism, witness floor" }, id: none, run: witness_floor_run_script(), shell: none, @@ -341,191 +305,6 @@ fn witness_floor_run_step() -> Step { } } -// TWO MORE INVOCATIONS OF THE SAME BINARY, mirroring witness_floor_run_step() exactly: the -// `--source-root` flags are the same fold over gunbc.ci_layer_roots witness_layer_roots (DESIGN -// §3 -- one authority, not a second literal roster), and each mode decides its own subject in -// .dag; this step passes only the source roots. `--required-regen` proves the fold's first -// generation matches the committed candidate -- a genuine committed-vs-emitted staleness probe. -// Both currently require at least one --source-root flag to pass claim_executor's own CLI gate -// even though neither mode's function consumes it (src/v1/stage0/src/bin/claim_executor.rs, the -// empty-source-roots check runs ahead of all three mode dispatches) -- fixing that gate is out -// of scope here; supplying the flag is the correct interim, not a workaround, since the flag is -// otherwise meaningful for every other mode this binary serves. -// -// WHY THESE TWO STEPS WERE STRIPPED ON 2026-08-20 AND RESTORED THE SAME DAY, recorded because the -// strip's argument was CORRECT AT THE TIME and a reader who finds only the restoration will -// re-derive the strip. The admission test that removed them (deep-ant-102): is every red closable, -// by the author who caused it, at the moment they caused it? It was not. `--required-regen` -// reported 16 drifting files on a tree carrying every fix then available, and no sequence of -// regenerations could clear them, so the only accessible green was a hand-edited mirror -- a -// required gate whose only path to green is the violation it guards against, which launders the -// workaround under a green check and is worse than no gate. What changed is not the test, it is -// the answer: TWO INDEPENDENT BLOCKERS WERE ROOT-CAUSED AND CLOSED, and the gate now has a -// reachable green that is reached by regenerating, never by editing. -// -// BLOCKER 1 -- THE TWO-GENERATION LAG (closed by #8637 plus a staged install, no hand edit). The -// duplicate `SeedRetainedIntrinsicRegistration` row was deleted from the .dag authority, but the -// first regenerated `lib.rs` STILL emitted the bare `pub mod expected_red_roster_join;` with no -// matching file (E0583), because `lib.rs`'s module list is emitted from the COMPILED-IN constant -// in `gunbc_stage0_crate_layout_generated.rs`, not from the .dag read at runtime. The route that -// closes it installs the regenerated constant ALONE, rebuilds, and re-runs: the emitted `lib.rs` -// is then clean and drift drops 16 -> 15. Every step is an install of generated bytes; nothing is -// authored by hand. Recorded because the tempting alternative -- deleting the offending line from -// the mirror to make the build proceed -- is the author-side absorbing fallback (DESIGN §5), and -// it had already conditioned three "generation 1 builds clean" reports from this session that had -// to be retracted. -// -// BLOCKER 2 -- THE COMPARATOR HAD NO REACHABLE GREEN, and this one is invisible to every -// candidate-side check because the candidate on disk is BYTE-IDENTICAL to the committed file the -// gate is refusing. After the staged install, drift stuck at exactly one file, -// `v1_compiler_infer.rs`, at generations 2, 3 and 4 -- deterministic, with `diff` returning zero -// lines each time. `compare_generated_surfaces` normalized BOTH sides through rustfmt while -// `write_emitted_tree` writes `normalize(emitted)`; once a candidate is installed the comparison -// is therefore `normalize(normalize(emitted))` vs `normalize(emitted)`, an identity only if -// rustfmt is idempotent -- and it is not: on that file a `let x = if (long.receiver.chain)` -// re-splits on a second pass. So the check refused a tree that already equalled its own artifact, -// forever, and the only way to silence it was to hand-edit the mirror. -// -// THE FIRST FIX WAS WRONG AND THE PRE-COMMIT HOOK CAUGHT IT, which is worth recording because it -// located the real defect. Comparing the committed side RAW against the single-pass bytes made -// `--required-regen` green and `cargo fmt --all --check` RED on the same file: the fmt gate -// re-formats what is committed, so it demands pass N+1, while the comparator had just demanded -// pass N. The two gates were in direct contradiction, and satisfying either broke the other in a -// loop with no exit. So the defect is not in either gate's comparison -- it is that the emitted -// artifact was written in a form that is not a FIXED POINT of the formatter, and a non-fixed-point -// artifact cannot satisfy two consumers that consume different passes of it. -// -// THE FIX: `normalize_generated_source` now iterates rustfmt to a fixed point (bounded at 8 -// passes, exceeding it is a typed refusal -- a widened arm there would be the absorbing fallback -// DESIGN §5 forbids and would restore the unclosable state), and the committed side is compared -// RAW against exactly those bytes. `cargo fmt` is then a no-op on the artifact by definition, so -// the contradiction is unrepresentable rather than detected: construction over validation -// (DESIGN §5), and the compared artifact and the written artifact become ONE derivation instead -// of two (DESIGN §3), which is what makes "install the candidate" a guaranteed remedy. -// Verified in both directions on the installed candidate: `cargo fmt --all --check` exits 0 AND -// `--required-regen` exits 0. -// -// EVIDENCE FOR THE RESTORATION, executed rather than argued: `--required-regen` exits 0 with -// `first_generation_equal=true planned=129 executed=129`; `--required-regen-fixed-point` exits 0 -// with `fixed_point_equal=true`; and the discriminating RED refuses -- one comment line prepended -// to a generated file yields exit 1 naming `std_pareto.rs`, with the restore verified afterwards. -// A green without that RED would establish nothing, since this comparator's failure mode was -// precisely a verdict that could not respond to the tree. -// -// COST, measured over seven full passes on an arm64 session container: `elapsed_ms` 206705, -// 220041, 221086, 231461, 234069, 235851, 253731 -- 3.4 to 4.2 minutes per pass. The fixed-point -// step performs a second full emit, so the pair adds roughly 8 minutes to a ~30-minute job. That -// is the price of the gate and it is stated here so a future reader trading it away is trading a -// known quantity. -// -// WHAT `--required-regen-fixed-point` ACTUALLY MEASURES, CORRECTED (2026-08-19, fierce-ram-721 -// review + operator verification, both against the source rather than the flag's name): despite -// its name, `run_required_regen_fixed_point` (required_regen_host.rs) never builds or invokes a -// candidate binary. `compile_stage0` calls `compile_sources`, a Rust fn linked into the SAME -// running claim_executor process that already emitted pass 1 -- there is no `Command::new` for -// cargo or a candidate executable anywhere in that path. So `fixed_point_equal` compares two -// emissions from the same in-process G0, not G0's emission against a rebuilt G1's. That is -// EMISSION REPEATABILITY (the compiler emits the same bytes twice), a real and useful property, -// but it is not a self-host fixed point, which requires G0 emit R1, BUILD G1 from R1, run G1 to -// emit R2, and compare R1 to R2 -- the build-and-run leg does not exist in this binary. The step -// below is named for what it measures, not for the CLI flag's name, to avoid publishing an -// authoritative CI green for a proposition nothing here evaluates (DESIGN §4b: a carrier named -// for a guarantee it does not hold is rung inflation, and ranks worse than sitting low, because -// it never gets fixed). The real self-host fixed point -- G0 builds and invokes G1 -- remains -// unanswered; a modeled v2 route (src/v2/compiler/self_host.dag) exists but has zero production -// consumers and sits in gunbc.witness_deferral_freeze, so it cannot hold that boundary today -// either. Whoever builds the real G0->G1->R2 chain should prove it with a candidate-use canary: -// swap the built candidate for a binary that prints a marker and exits nonzero, and confirm the -// boundary observes it -- a mechanism that only recomputes through G0 stays green and is caught. -// WHY THESE TWO STEPS DECLARE A PRECONDITION INSTEAD OF INHERITING THEIR POSITION. -// -// A step with no `if` inherits GitHub's default `success()`, which means the conjunction of -// every earlier step in the job. Ordered after the witness floor, both regen steps therefore -// carried an undeclared dependency on the floor's verdict -- and the regen claim does not -// depend on the floor's verdict. Its actual precondition is that the binary exists. -// -// Two costs, and the second is the reason this is a defect rather than a preference. The -// measurement cost: on the only run that has ever carried these steps (32312549861), both -// report `skipped` with zero duration, because the floor went red for main's own corpus -// defect -- a population with no relation to regen. So the regen wall cannot be measured on -// CI at all while any unrelated floor red stands. The correctness cost: once merged, a floor -// red DISARMS the regen gate, so the stale-mirror class this enrollment exists to catch goes -// unchecked on exactly the runs where the tree is already known to be unhealthy -- and a -// skipped step does not report as failing, so the check surface reads `floor red, regen fine` -// when regen was never asked. That is the empty-observation narrow from DESIGN's failure-mode -// list -- same class, new instance: bottom-as-answer conflated with bottom-as-ignorance, here -// `regen did not fail` standing in for `regen was never evaluated`. The named instance in -// DESIGN is a diff observation rendered as `nothing is affected`; this one is a skipped gate -// rendered as an unbroken gate. -// -// Binding to the build step's own outcome states the real dependency: the two claims are -// independent, they fail independently, and neither silences the other. This is a narrowing -// of an undeclared condition to a declared one, NOT a climb -- nothing here checks that a -// future step keeps declaring its precondition rather than inheriting one. -fn witness_regen_precondition() -> String { - concat(witness_step_status_guard(), "steps.build_witness_fold.outcome == 'success'") -} - -// The fixed-point step's precondition is the REGEN step, not the build, and this is measured -// rather than assumed: run standalone on a clean tree it exits nonzero in under a second with -// `refused: read receipt target/stage0-regen-receipt.json: No such file or directory`. It -// consumes a receipt the regen step above produces, so that is a genuine data dependency and -// the two steps are NOT peers. Binding it to the build alone would have replaced an undeclared -// condition that was too strong with one that is too weak -- it would run on every build and -// refuse for a missing receipt, reporting a receipt-plumbing failure where the real event is -// that regen did not get far enough to write one. -fn witness_regen_fixed_point_precondition() -> String { - concat(witness_step_status_guard(), "steps.required_regen.outcome == 'success'") -} - -fn witness_required_regen_script() -> String { - concat( - concat("ROOT=", concat(ci_repo_root_shell(), "\n")), - concat( - "\"$ROOT/target/release/claim_executor\" --required-regen", - concat(witness_floor_source_root_flags(), "\n") - ) - ) -} - -fn witness_required_regen_step() -> Step { - RunStep { - name: Present { value: "Regen fixed point: first generation matches committed candidate" }, - id: Present { value: "required_regen" }, - run: witness_required_regen_script(), - shell: none, - env: none, - working_directory: none, - if_condition: Present { value: witness_regen_precondition() }, - continue_on_error: none, - timeout_minutes: none - } -} - -fn witness_required_regen_fixed_point_script() -> String { - concat( - concat("ROOT=", concat(ci_repo_root_shell(), "\n")), - concat( - "\"$ROOT/target/release/claim_executor\" --required-regen-fixed-point", - concat(witness_floor_source_root_flags(), "\n") - ) - ) -} - -fn witness_required_regen_fixed_point_step() -> Step { - RunStep { - name: Present { value: "Regen determinism: G0 emit reproduces itself on a second pass" }, - id: none, - run: witness_required_regen_fixed_point_script(), - shell: none, - env: none, - working_directory: none, - if_condition: Present { value: witness_regen_fixed_point_precondition() }, - continue_on_error: none, - timeout_minutes: none - } -} - // A DECLARED PROVISIONAL BOUND, NOT A DERIVED ONE, AND THE DIFFERENCE IS THE WHOLE POINT. // // Absent, this job inherits GitHub's 360-minute default -- so it was never unbounded, but @@ -561,9 +340,6 @@ fn witness_floor_job() -> Job { witness_floor_checkout_step(), witness_floor_toolchain_step(), witness_floor_build_step(), - witness_floor_v1_parse_step(), - witness_required_regen_step(), - witness_required_regen_fixed_point_step(), witness_floor_run_step() ], needs: [], diff --git a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag new file mode 100644 index 00000000000..2097a490e4b --- /dev/null +++ b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag @@ -0,0 +1,71 @@ +module test.claim.witness_floor_workflow_consolidation_witness_test + +import std.types { Bool, String } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import gunbc.witness_floor_workflow { expected_witness_floor_yml } + +// THE CI PHASES ARE THE PROGRAM'S TO SEQUENCE, NOT THE JOB'S (operator directive, 2026-08-20: +// "i would much prefer if it was all handled within the witnesses step and within the gunbc +// binary, not at a github actions job level"). +// +// WHAT THESE ASSERT AND WHAT THEY CANNOT. The subject is the EMITTED workflow text, so these +// witnesses decide the shape of what CI is asked to run. They say nothing about whether the +// composed run then executes its four phases correctly — that is the binary's own behaviour and +// it is not observable from here. Two separate claims, and conflating them would let a correctly +// shaped one-step workflow stand in for evidence that the phases run. +// +// Stated so a reader does not over-read the green: this is the CHEAP half of the pair. + +data consolidation_note: String = "The step ladder these replace was four steps: parse, regen, regen-fixed-point, floor. Each was its own process, the ORDER was a YAML list, each precondition was an `if:` naming another step's `outcome`, and the fixed-point step received pass 1's digest by re-reading the receipt FILE the regen process had written — a process boundary standing where a function call belonged, since run_required_regen_fixed_point has taken pass1_digest: Option all along and CI passed None." + +// ONE RUN STEP REACHES THE BINARY, and it names the composed mode. The negative half is what +// makes this discriminating: if any of the three retired invocations reappears as its own step, +// the ladder has grown back and this reds. +test fn w_ci_invokes_one_composed_mode_not_a_step_ladder() -> Bool { + let yml = expected_witness_floor_yml() + string_contains(s: yml, pattern: "claim_executor\" --required-ci") + && !string_contains(s: yml, pattern: "claim_executor\" --required-floor") + && !string_contains(s: yml, pattern: "claim_executor\" --required-regen") + && !string_contains(s: yml, pattern: "target/release/v1_src_dag_parse") +} + +// NO STEP'S PRECONDITION READS ANOTHER STEP'S VERDICT — except the one that guards on the BUILD, +// which is a different fact: a step cannot run a binary that failed to compile, and that is the +// job's business rather than the program's. What is gone is `steps.required_regen.outcome`, the +// data dependency that now lives in the binary as an in-memory digest handoff. +test fn w_no_phase_precondition_reads_another_phases_outcome() -> Bool { + let yml = expected_witness_floor_yml() + !string_contains(s: yml, pattern: "steps.required_regen.outcome") + && string_contains(s: yml, pattern: "steps.build_witness_fold.outcome == 'success'") +} + +// THE PARSE SWEEP STILL HAS AN EXECUTING CONSUMER. Folding a check into another process is only +// legitimate if it still runs; a consolidation that quietly dropped a phase would look exactly +// like a consolidation that kept it, from the step count alone. The binary is built with the +// parse bin still declared, and the composed mode calls the same walk the bin calls. +test fn w_the_parse_sweep_survives_the_fold() -> Bool { + let yml = expected_witness_floor_yml() + string_contains(s: yml, pattern: "--bin v1_src_dag_parse") + && string_contains(s: yml, pattern: "--required-ci") +} + +// RED CONTROL ON A DIFFERENT AXIS FROM THE INVOCATIONS ABOVE: the retired steps' NAMES. +// +// The first witness reads the run commands; this one reads the step names GitHub renders, which +// is what a person re-adding a step would write first. The two can disagree — a step named +// "Regen determinism" whose command was folded into the composed run would pass the command +// check and fail this one — and that disagreement is the point of having both. +// +// An earlier revision of this file put a near-impossible literal here instead. It would have +// gone green forever without ever being able to fail, which is worse than having no control: +// a vacuous assertion reads as coverage on the roster. +test fn w_RED_the_retired_step_names_do_not_return() -> Bool { + let yml = expected_witness_floor_yml() + let positive_control = string_contains(s: yml, pattern: "Required CI: parse, regen, regen determinism, witness floor") + positive_control + && !string_contains(s: yml, pattern: "name: src/v1 .dag sources parse") + && !string_contains(s: yml, pattern: "Regen fixed point:") + && !string_contains(s: yml, pattern: "Regen determinism:") +} + +data witness_live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly diff --git a/dag/tools/extdeps_scope_placement_gate.dag b/dag/tools/extdeps_scope_placement_gate.dag index 06f92a285c9..9a51f9d9a19 100644 --- a/dag/tools/extdeps_scope_placement_gate.dag +++ b/dag/tools/extdeps_scope_placement_gate.dag @@ -21,7 +21,7 @@ import gunbc.extdeps_scope_frontier { } // THIS GATE DOES NOT CURRENTLY RUN (measured 2026-08-19): no workflow in .github/workflows/ -// references it, and the only executing CI step is `claim_executor --required-floor`. The note +// references it, and the only executing CI step is `claim_executor --required-ci`, whose floor phase is the fold this refers to (it was `--required-floor` until the 2026-08-20 consolidation folded parse, regen and the floor into one invocation). The note // below calls it a "server-side per-PR wall" that "refuses" — present tense, and not true today. // DESIGN's "Building & checks" section already declares the floor cut's bounded rung drop and names // the effect gates among what is unguarded; this comment carries that fact to the module a reader diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 25d7d1245de..fe0f4206108 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -10337,6 +10337,7 @@ fn run() -> Result { let mut floor_worker_role: Option = None; let mut scoped_batch_id: Option = None; let mut required_floor_mode = false; + let mut required_ci_mode = false; let mut required_regen_mode = false; let mut required_regen_fixed_point_mode = false; let mut regen_candidate_dir = "target/stage0-regen-candidate".to_string(); @@ -10362,6 +10363,9 @@ fn run() -> Result { "--required-floor" => { required_floor_mode = true; } + "--required-ci" => { + required_ci_mode = true; + } "--required-regen" => { required_regen_mode = true; } @@ -10460,6 +10464,165 @@ fn run() -> Result { // and the absence of those flags is the point rather than an omission. `run_required_floor` // refuses when planned, executed and terminal identity counts disagree, so a silently short // roster cannot report as a pass. + // THE COMPOSED CI RUN — one process, one ordered fold over the phases the job used to + // express as four GitHub Actions steps. + // + // WHAT WAS WRONG WITH THE STEP LADDER, and it is not verbosity. Four steps meant four + // processes; the ORDER lived in a YAML list; each step's precondition was an `if:` naming + // another step's `outcome`; and the fixed-point phase received pass 1's digest by READING + // THE RECEIPT FILE the previous process had written. That last one is the tell — the + // function has taken `pass1_digest: Option` all along, and the file round-trip + // existed only because a process boundary sat where a function call belonged. Sequencing a + // program's phases is the program's job (DESIGN §3: the workflow is a realization of the + // intent, not the place the intent lives). + // + // WHAT THE ORDER IS, AND WHY EACH PHASE RUNS ANYWAY. Only ONE real dependency exists: + // fixed-point needs regen's pass-1 digest, so it is skipped — visibly, as its own reported + // state — when regen did not produce one. Everything else is independent, so every other + // phase RUNS EVEN AFTER AN EARLIER FAILURE and the run reports the complete ledger. That is + // deliberate and it is the one behavioural change here: under the step ladder a regen + // failure skipped the fixed point AND, before stern-tern-636's correction, could leave the + // floor's own precondition reading someone else's verdict — so one defect hid the others, + // and a fix landed blind to whatever else was red. The line still stops (a nonzero exit on + // any failed phase); it stops with every deficit named. This is the stopped-line AUDIT + // DESIGN §5 sanctions: it reports, it never greens. + // + // WHAT IS *NOT* SHARED, stated so the saving is not overclaimed. `run_required_regen` and + // `run_required_regen_fixed_point` each call `compile_stage0`, and the second call STAYS: + // re-emitting the same input and comparing digests IS what the fixed point measures, so + // collapsing the two compiles would delete the measurement. The floor's own preparation is + // a different computation again (resolving witnesses, not emitting Rust) and shares with + // neither. What this removes is process startup, the receipt file round-trip, and the + // YAML-level orchestration — NOT a redundant compile. + if required_ci_mode { + let mut phase_failures: Vec = Vec::new(); + let mut ran: Vec<&'static str> = Vec::new(); + + // PHASE 1 — src/v1 .dag parse sweep. Independent of everything below it. + eprintln!("required-ci: phase parse (src/v1 .dag)"); + match v1_compiler::cli_run::run_v1_src_dag_parse(&v1_compiler::cli_run::workspace_root()) { + Ok(count) => eprintln!("required-ci: parse OK {count} file(s) parse-clean"), + Err(errors) => { + for e in &errors { + eprintln!("required-ci: parse FAIL {e}"); + } + phase_failures.push(format!("parse ({} error(s))", errors.len())); + } + } + ran.push("parse"); + + // PHASE 2 — regen first generation. Produces the pass-1 digest phase 3 needs. + eprintln!("required-ci: phase regen (first generation vs committed)"); + let pass1_digest: Option = match v1_compiler::cli_run::run_required_regen( + ®en_candidate_dir, + ®en_receipt_path, + ) { + Ok(outcome) => { + eprintln!( + "required-ci: regen first_generation_equal={} candidate={}", + outcome.receipt.first_generation_equal, outcome.receipt.candidate_artifact + ); + for failure in &outcome.failures { + eprintln!("required-ci: regen FAIL {failure}"); + } + if !outcome.failures.is_empty() { + phase_failures.push(format!("regen ({} failure(s))", outcome.failures.len())); + } + // THE DIGEST IS HANDED OVER IN MEMORY, and it is produced whether or not the + // comparison agreed: pass 1 emitted a tree either way, and asking whether the + // emitter reproduces itself is a SEPARATE question from whether it matches what + // is committed. Skipping the fixed point on a regen mismatch would conflate + // them and lose a determinism signal exactly when drift makes it interesting. + Some(outcome.receipt.candidate_generated_digest.clone()) + } + Err(e) => { + // A REFUSAL IS NOT A MISMATCH. Nothing was emitted, so there is no pass-1 + // digest, and phase 3 has no subject rather than a failing one. + eprintln!("required-ci: regen refused: {e}"); + phase_failures.push(format!("regen refused: {e}")); + None + } + }; + ran.push("regen"); + + // PHASE 3 — regen determinism. The ONLY phase with a real precondition. + match pass1_digest { + Some(pass1) => { + eprintln!("required-ci: phase regen-fixed-point (G0 reproduces itself)"); + match v1_compiler::cli_run::run_required_regen_fixed_point( + ®en_receipt_path, + Some(pass1), + ) { + Ok(outcome) => { + eprintln!( + "required-ci: regen-fixed-point fixed_point_equal={}", + outcome.receipt.fixed_point_equal + ); + for failure in &outcome.failures { + eprintln!("required-ci: regen-fixed-point FAIL {failure}"); + } + if !outcome.failures.is_empty() { + phase_failures.push(format!( + "regen-fixed-point ({} failure(s))", + outcome.failures.len() + )); + } + } + Err(e) => { + eprintln!("required-ci: regen-fixed-point refused: {e}"); + phase_failures.push(format!("regen-fixed-point refused: {e}")); + } + } + ran.push("regen-fixed-point"); + } + None => { + // SKIPPED IS ITS OWN REPORTED STATE, never silence and never a pass. The phase + // did not run because its input does not exist; saying nothing here would make + // an unrun determinism check indistinguishable from a passing one. + eprintln!( + "required-ci: phase regen-fixed-point SKIPPED — regen produced no pass-1 digest, so there is nothing to compare a second pass against" + ); + phase_failures + .push("regen-fixed-point skipped (no pass-1 digest from regen)".to_string()); + } + } + + // PHASE 4 — the witness floor. Independent; runs whatever happened above. + eprintln!("required-ci: phase floor (one prepared subject, one fold)"); + let commit = std::env::var("GITHUB_SHA").unwrap_or_else(|_| "local".to_string()); + match v1_compiler::cli_run::run_required_floor( + &source_roots, + &commit, + v1_compiler::cli_run::ShardStyle::single_shard(), + ) { + Ok(outcome) => { + report_required_floor_outcome(&outcome); + if !required_floor_outcome_is_clean(&outcome) { + phase_failures.push("floor".to_string()); + } + } + Err(e) => { + eprintln!("required-ci: floor refused: {e}"); + phase_failures.push(format!("floor refused: {e}")); + } + } + ran.push("floor"); + + eprintln!( + "required-ci: phases_run={} failed={}", + ran.len(), + phase_failures.len() + ); + for failure in &phase_failures { + eprintln!("required-ci: FAILED PHASE {failure}"); + } + return if phase_failures.is_empty() { + Ok(ExitCode::SUCCESS) + } else { + Err(ExitCode::from(1)) + }; + } + if required_regen_fixed_point_mode { return match v1_compiler::cli_run::run_required_regen_fixed_point(®en_receipt_path, None) { @@ -10518,63 +10681,8 @@ fn run() -> Result { v1_compiler::cli_run::ShardStyle::single_shard(), ) { Ok(outcome) => { - eprintln!( - "required-floor: subject={} modules_resolved={} modules_excluded={}", - outcome.subject_digest, outcome.modules_resolved, outcome.modules_excluded - ); - eprintln!( - "required-floor: planned={} executed={} terminal={} passed={} \ - known_red_held={} failed={} stale_quarantine={} \ - interrupted_before_verdict={} completed_over_cost_requirement={} \ - host_tool_unresolved={} over_cost_line_diagnostic={}", - outcome.claims_planned, - outcome.claims_executed, - outcome.receipt_identities, - outcome.passed, - outcome.known_red_held, - outcome.failures.len(), - outcome.stale_quarantine.len(), - outcome.interrupted_before_verdict.len(), - outcome.completed_over_cost_requirement.len(), - outcome.host_tool_unresolved.len(), - outcome.over_cost_line_diagnostic - ); - for failure in &outcome.failures { - eprintln!("required-floor: FAIL {failure}"); - } - // FIVE CAUSES, FIVE COUNTS, ONE STOPPED LINE. All five refuse the run, and - // they are reported apart because their remedies differ: a FAIL is a defect to - // fix, a STALE-QUARANTINE is a fix that already landed and a roster row to - // delete, an INTERRUPTED-BEFORE-VERDICT is an undecided claim whose real cost - // is unmeasured (operator ruling 2026-08-19, BUDGET POLICY CUT), a - // COMPLETED-OVER-COST-REQUIREMENT is a claim that reached a verdict and then - // was found to cost too much (an exact measurement, not a bound), and a - // HOST-TOOL-UNRESOLVED is an infra gap to provision (never a witness-cost - // chase). Summing them into `failed` would make an un-quarantine - // indistinguishable from a regression in the alert signature, which is the - // conflation `std.witness_admission` rules out. Splitting the former - // `budget_refused` collection in two makes it visible whether a stopped run - // is a cost debt on a claim that actually finished, or a claim the safety - // deadline preempted before it could answer at all — but both still stop the - // line: an interruption is NotEvaluated, and NotEvaluated is never green. - for stale in &outcome.stale_quarantine { - eprintln!("required-floor: STALE-QUARANTINE {stale}"); - } - for refused in &outcome.interrupted_before_verdict { - eprintln!("required-floor: INTERRUPTED-BEFORE-VERDICT {refused}"); - } - for over_cost in &outcome.completed_over_cost_requirement { - eprintln!("required-floor: COMPLETED-OVER-COST-REQUIREMENT {over_cost}"); - } - for unresolved in &outcome.host_tool_unresolved { - eprintln!("required-floor: HOST-TOOL-UNRESOLVED {unresolved}"); - } - if outcome.failures.is_empty() - && outcome.stale_quarantine.is_empty() - && outcome.interrupted_before_verdict.is_empty() - && outcome.completed_over_cost_requirement.is_empty() - && outcome.host_tool_unresolved.is_empty() - { + report_required_floor_outcome(&outcome); + if required_floor_outcome_is_clean(&outcome) { Ok(ExitCode::SUCCESS) } else { Err(ExitCode::from(1)) @@ -11781,6 +11889,75 @@ fn emit_worker_terminal_before_return(code: ExitCode) -> ExitCode { code } +/// Print the floor's complete ledger. ONE implementation, called by `--required-floor` and by +/// the composed `--required-ci` run, so the two modes cannot drift into reporting the same +/// outcome differently (DESIGN §3). +fn report_required_floor_outcome(outcome: &v1_compiler::cli_run::RequiredFloorOutcome) { + eprintln!( + "required-floor: subject={} modules_resolved={} modules_excluded={}", + outcome.subject_digest, outcome.modules_resolved, outcome.modules_excluded + ); + eprintln!( + "required-floor: planned={} executed={} terminal={} passed={} \ + known_red_held={} failed={} stale_quarantine={} \ + interrupted_before_verdict={} completed_over_cost_requirement={} \ + host_tool_unresolved={} over_cost_line_diagnostic={}", + outcome.claims_planned, + outcome.claims_executed, + outcome.receipt_identities, + outcome.passed, + outcome.known_red_held, + outcome.failures.len(), + outcome.stale_quarantine.len(), + outcome.interrupted_before_verdict.len(), + outcome.completed_over_cost_requirement.len(), + outcome.host_tool_unresolved.len(), + outcome.over_cost_line_diagnostic + ); + for failure in &outcome.failures { + eprintln!("required-floor: FAIL {failure}"); + } + // FIVE CAUSES, FIVE COUNTS, ONE STOPPED LINE. All five refuse the run, and + // they are reported apart because their remedies differ: a FAIL is a defect to + // fix, a STALE-QUARANTINE is a fix that already landed and a roster row to + // delete, an INTERRUPTED-BEFORE-VERDICT is an undecided claim whose real cost + // is unmeasured (operator ruling 2026-08-19, BUDGET POLICY CUT), a + // COMPLETED-OVER-COST-REQUIREMENT is a claim that reached a verdict and then + // was found to cost too much (an exact measurement, not a bound), and a + // HOST-TOOL-UNRESOLVED is an infra gap to provision (never a witness-cost + // chase). Summing them into `failed` would make an un-quarantine + // indistinguishable from a regression in the alert signature, which is the + // conflation `std.witness_admission` rules out. Splitting the former + // `budget_refused` collection in two makes it visible whether a stopped run + // is a cost debt on a claim that actually finished, or a claim the safety + // deadline preempted before it could answer at all — but both still stop the + // line: an interruption is NotEvaluated, and NotEvaluated is never green. + for stale in &outcome.stale_quarantine { + eprintln!("required-floor: STALE-QUARANTINE {stale}"); + } + for refused in &outcome.interrupted_before_verdict { + eprintln!("required-floor: INTERRUPTED-BEFORE-VERDICT {refused}"); + } + for over_cost in &outcome.completed_over_cost_requirement { + eprintln!("required-floor: COMPLETED-OVER-COST-REQUIREMENT {over_cost}"); + } + for unresolved in &outcome.host_tool_unresolved { + eprintln!("required-floor: HOST-TOOL-UNRESOLVED {unresolved}"); + } +} + +/// Whether the floor outcome permits a green run. +/// +/// FIVE CAUSES, ONE STOPPED LINE — and the conjunction is written once here rather than at each +/// caller, because a mode that forgot one of the five would green a run the other refused. +fn required_floor_outcome_is_clean(outcome: &v1_compiler::cli_run::RequiredFloorOutcome) -> bool { + outcome.failures.is_empty() + && outcome.stale_quarantine.is_empty() + && outcome.interrupted_before_verdict.is_empty() + && outcome.completed_over_cost_requirement.is_empty() + && outcome.host_tool_unresolved.is_empty() +} + fn main() -> ExitCode { let coordinator_args: Vec = std::env::args().skip(1).collect(); if let Some(code) = maybe_run_floor_coordinator(&coordinator_args) { diff --git a/src/v1/stage0/src/bin/v1_src_dag_parse.rs b/src/v1/stage0/src/bin/v1_src_dag_parse.rs index 164e39ebda8..f47917bc9eb 100644 --- a/src/v1/stage0/src/bin/v1_src_dag_parse.rs +++ b/src/v1/stage0/src/bin/v1_src_dag_parse.rs @@ -5,11 +5,16 @@ const SCAFFOLD_NOTE: &str = "SCAFFOLD \u{2014} dissolve-on: when src/v1 .dag is modeled pipeline / when the seed shrinks to zero (\u{a7}7; src/v1 is the bootstrap seed; \ parsing the seed currently requires the seed parser itself)."; -use im::HashMap; +// A THIN CALLER, NOT THE IMPLEMENTATION. The walk lives in `cli_run::run_v1_src_dag_parse` so +// that the composed `claim_executor --required-ci` run can hold it as one phase beside regen and +// the floor, in one process, instead of the CI job holding it as one more YAML step whose order +// and precondition are facts about a workflow file. +// +// This binary is kept because running the parse sweep ALONE is a real local action — it is the +// cheapest check in the tree and the one worth reaching for while editing src/v1. It is not a +// second authority: it calls the same function the composed run calls, so there is one walk. + use std::process::ExitCode; -use std::rc::Rc; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; fn main() -> ExitCode { let cwd = match std::env::current_dir() { @@ -19,105 +24,16 @@ fn main() -> ExitCode { return ExitCode::from(1); } }; - - let v1_dir = cwd.join("src/v1"); - // RECURSIVE, and the non-recursive predecessor is why this file changed at all. - // `read_dir(src/v1)` sees the 46 top-level modules and nothing below them, so three - // authored files were outside the only instrument that can see a v1 parse error: - // gunbc/occurrence_binding_parser_walk.dag, gunbc/namespace_reference_derived_closure_ - // production_observations.dag, and tests/claim/checkpoint_identity_keying_witness_test.dag - // -- the last holding every `test fn` src/v1 declares. The subtree most likely to carry - // a defect was the subtree the check could not reach, and nothing reported that: a walk - // that finds no files in a directory it never opened is indistinguishable from a clean one. - let mut dag_paths: Vec = Vec::new(); - let mut stack: Vec = vec![v1_dir.clone()]; - while let Some(dir) = stack.pop() { - let read_dir = match std::fs::read_dir(&dir) { - Ok(d) => d, - Err(e) => { - eprintln!("v1_src_dag_parse: read_dir {}: {e}", dir.display()); - return ExitCode::from(1); - } - }; - for entry in read_dir.flatten() { - let path = entry.path(); - if path.is_dir() { - // `target/` under a nested Cargo.toml is build output, never authored source. - if path.file_name().map(|n| n == "target").unwrap_or(false) { - continue; - } - // `tests/fixtures/` holds deliberately partial or malformed inputs - // authored FOR the parser's own tests -- a fixture that fails to parse - // is the fixture doing its job, not a defect. The existing corpus - // discovery in `compiler_tests` excludes them on the same grounds. - if path.file_name().map(|n| n == "fixtures").unwrap_or(false) - && dir.file_name().map(|n| n == "tests").unwrap_or(false) - { - continue; - } - stack.push(path); - } else if path.extension().map(|ext| ext == "dag").unwrap_or(false) { - dag_paths.push(path); - } + match v1_compiler::cli_run::run_v1_src_dag_parse(&cwd) { + Ok(count) => { + eprintln!("v1_src_dag_parse: {count} file(s) parse-clean"); + ExitCode::SUCCESS } - } - dag_paths.sort(); - - if dag_paths.is_empty() { - eprintln!("v1_src_dag_parse: no .dag files found in src/v1/ — check run directory"); - return ExitCode::from(1); - } - - let errors: Arc>> = Arc::new(Mutex::new(Vec::new())); - let count = Arc::new(AtomicUsize::new(0)); - - // Each file gets its own Rc — no shared parse state — so parsing is - // embarrassingly parallel. Thread panics propagate via scope and exit non-zero (fail-closed). - std::thread::scope(|s| { - for path in &dag_paths { - let errors = Arc::clone(&errors); - let count = Arc::clone(&count); - let path = path.clone(); - s.spawn(move || { - let content = match std::fs::read_to_string(&path) { - Ok(c) => c, - Err(e) => { - errors - .lock() - .unwrap() - .push(format!("read {}: {e}", path.display())); - return; - } - }; - let result = v1_compiler::v1_compiler_parse::parse( - v1_compiler::v1_compiler_tokenize::tokenize( - content, - path.to_string_lossy().to_string(), - ), - Rc::new(HashMap::new()), - ); - if let Some(ref err) = result.error { - errors.lock().unwrap().push(format!( - "parse error in {}: {}", - path.file_name().unwrap_or_default().to_string_lossy(), - v1_compiler::v1_std_core::diagnostic_to_message(err.diagnostic.clone(),), - )); - } else { - count.fetch_add(1, Ordering::Relaxed); - } - }); - } - }); - - let errors = Arc::try_unwrap(errors).unwrap().into_inner().unwrap(); - if !errors.is_empty() { - for e in &errors { - eprintln!("v1_src_dag_parse: {e}"); + Err(errors) => { + for e in &errors { + eprintln!("v1_src_dag_parse: {e}"); + } + ExitCode::from(1) } - return ExitCode::from(1); } - - let count = Arc::try_unwrap(count).unwrap().into_inner(); - eprintln!("v1_src_dag_parse: {count} file(s) parse-clean"); - ExitCode::SUCCESS } diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 183af6ac296..dcdee685a5c 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -14691,6 +14691,133 @@ fn render_witness_budget_warn_text( )) } +/// The `src/v1` `.dag` parse sweep, as a callable phase rather than a separate binary. +/// +/// WHY IT MOVED HERE. It was a `main()` in `bin/v1_src_dag_parse.rs`, reached only by its own +/// GitHub Actions step. That made the ORDER of the CI checks a fact about a YAML file: a step +/// list, `if:` guards referring to other steps' outcomes, and one process per check. This +/// function is the same walk with its result returned instead of exited, so the composed +/// `--required-ci` run can hold it beside the regen and floor phases in one process. The bin +/// remains as a thin caller, because running the parse sweep alone is a real local action. +/// +/// RECURSIVE, and the non-recursive predecessor is why the walk looks like this: `read_dir` +/// on `src/v1` sees the top-level modules and nothing below them, so a walk that finds no +/// files in a directory it never opened is indistinguishable from a clean one. +/// +/// Returns the count of parse-clean files, or every error found — never a partial success. +pub fn run_v1_src_dag_parse(workspace: &Path) -> Result> { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Arc, Mutex}; + + let v1_dir = workspace.join("src/v1"); + let mut dag_paths: Vec = Vec::new(); + let mut stack: Vec = vec![v1_dir.clone()]; + while let Some(dir) = stack.pop() { + let read_dir = match std::fs::read_dir(&dir) { + Ok(d) => d, + Err(e) => return Err(vec![format!("read_dir {}: {e}", dir.display())]), + }; + // ONE DELIBERATE DIFFERENCE FROM THE BIN THIS CAME FROM: it used `read_dir.flatten()`, + // which silently DISCARDS an entry that cannot be read, so an unreadable directory + // entry made the walk quietly smaller and a walk that never saw a file is + // indistinguishable from a file that parsed. The error is propagated instead. + for entry in read_dir { + let entry = match entry { + Ok(entry) => entry, + Err(e) => return Err(vec![format!("read_dir entry in {}: {e}", dir.display())]), + }; + let path = entry.path(); + if path.is_dir() { + // `target/` under a nested Cargo.toml is build output, never authored source. + if path.file_name().map(|n| n == "target").unwrap_or(false) { + continue; + } + // `tests/fixtures/` holds deliberately partial or malformed inputs authored FOR + // the parser's own tests -- a fixture that fails to parse is the fixture doing + // its job, not a defect. The existing corpus discovery in `compiler_tests` + // excludes them on the same grounds. + // + // THIS EXCLUSION WAS DROPPED WHEN THE WALK MOVED HERE FROM THE BIN, and the + // consolidation's first local run reported `fact_cardinality_split_brace.dag: + // expected keyword 'module', found keyword 'data'` as a parse FAILURE. That + // file is a headerless fragment under `tests/fixtures/` and it is on main, + // where the parse step is green -- so the "finding" was the extraction having + // silently widened its own subject, not a defect in the tree. + if path.file_name().map(|n| n == "fixtures").unwrap_or(false) + && dir.file_name().map(|n| n == "tests").unwrap_or(false) + { + continue; + } + stack.push(path); + } else if path.extension().map(|ext| ext == "dag").unwrap_or(false) { + dag_paths.push(path); + } + } + } + dag_paths.sort(); + + // AN EMPTY WALK REFUSES. Zero files found is not zero errors — it is the walk failing to + // reach its subject, and reporting it as clean is the empty-observation narrow. + if dag_paths.is_empty() { + return Err(vec![format!( + "no .dag files found under {} — check the workspace root", + v1_dir.display() + )]); + } + + let errors: Arc>> = Arc::new(Mutex::new(Vec::new())); + let count = Arc::new(AtomicUsize::new(0)); + + // Each file gets its own `Rc` — no shared parse state — so parsing is + // embarrassingly parallel. Thread panics propagate via scope (fail-closed). + std::thread::scope(|scope| { + for path in &dag_paths { + let errors = Arc::clone(&errors); + let count = Arc::clone(&count); + let path = path.clone(); + scope.spawn(move || { + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(e) => { + errors + .lock() + .expect("parse error lock") + .push(format!("read {}: {e}", path.display())); + return; + } + }; + let result = crate::v1_compiler_parse::parse( + crate::v1_compiler_tokenize::tokenize( + content, + path.to_string_lossy().to_string(), + ), + std::rc::Rc::new(im::HashMap::new()), + ); + if let Some(ref err) = result.error { + errors.lock().expect("parse error lock").push(format!( + "parse error in {}: {}", + path.file_name().unwrap_or_default().to_string_lossy(), + crate::v1_std_core::diagnostic_to_message(err.diagnostic.clone()), + )); + } else { + count.fetch_add(1, Ordering::Relaxed); + } + }); + } + }); + + let errors = Arc::try_unwrap(errors) + .expect("parse error arc is uniquely held after scope") + .into_inner() + .expect("parse error lock"); + if !errors.is_empty() { + return Err(errors); + } + Ok(Arc::try_unwrap(count) + .expect("parse count arc is uniquely held after scope") + .into_inner()) +} + pub fn install_output_policy(source_roots: &[String]) { let entry = "dag/gunbc/output_policy.dag"; let (graph, indices) = match resolve_entry_graph_shared(source_roots, entry) { From 542c1280d935862750b4e6a977a7a5a6c6b7a8aa Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 08:11:03 +0000 Subject: [PATCH 2/7] Restore --required-ci: a mutation test shipped its own mutation (review 54012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The emitter and the generated workflow were calling `--required-floor`, so the composed four-phase run this PR introduces never executed, while DESIGN.md asserted it did. Blocking, and correct. HOW IT HAPPENED, because the mechanism is more useful than the fix. I mutation-tested the new witness by pointing the step back at `--required-floor` and confirming `w_ci_invokes_one_composed_mode_not_a_step_ladder` went false. The wall worked. The restore did not: the command ran the mutation, the witness, and `cp /tmp/wf.bak` back — and the shell TIMED OUT mid-loop, before the restore. The "restored" echo never printed and I did not notice its absence. Then I verified the wrong thing. `grep -c 'required-ci'` returned 1 and I read that as restored. It was matching ONE PROSE LINE — the comment block explaining the consolidation — not the emitted script. A corpus grep for a symbol finds the documentation about the symbol first, and this file is mostly documentation. The witness would have caught it. It had already TOLD me, returning false as the mutation intended; I attributed that to the mutation and never re-ran it after the supposed restore. A mutation test's last step is not observing red — it is re-observing green afterwards, and that step has to be in the same command as the restore or it does not reliably happen. FIXED: emitter emits `--required-ci`, yml regenerated (drift was a symptom, not a second defect), and all four witnesses re-run AGAINST THE FINAL STATE — all true. ALSO FIXED, same review: the SKIPPED eprintln carried a runaway indentation blob. `cargo fmt` had collapsed a `\` continuation into one literal with the source indentation baked in. Re-broken with an escaped continuation, and re-checked that fmt does not re-collapse it. NOT FIXED, named rather than swept in: three pre-existing strings of the same shape at claim_executor.rs:405, :7998 and :8028 (FLOOR-COMPILE-CLEAN-OVER-BUDGET, FLOOR-BATCH-CLAMP-REFUSED, FLOOR-BATCH-OVER-BUDGET). Same fmt-collapse class, none of them this PR's, and widening the diff to unrelated lines is how a focused change stops being reviewable. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/witnesses.yml | 2 +- dag/gunbc/witness_floor_workflow.dag | 2 +- src/v1/stage0/src/bin/claim_executor.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 680b89550fb..ec7d4ce5eee 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -34,5 +34,5 @@ jobs: - name: "Required CI: parse, regen, regen determinism, witness floor" run: | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) - "$ROOT/target/release/claim_executor" --required-floor --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" + "$ROOT/target/release/claim_executor" --required-ci --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" if: "!cancelled() && steps.build_witness_fold.outcome == 'success'" diff --git a/dag/gunbc/witness_floor_workflow.dag b/dag/gunbc/witness_floor_workflow.dag index 731a4081e7b..47a8137a002 100644 --- a/dag/gunbc/witness_floor_workflow.dag +++ b/dag/gunbc/witness_floor_workflow.dag @@ -235,7 +235,7 @@ fn witness_floor_run_script() -> String { concat( concat("ROOT=", concat(ci_repo_root_shell(), "\n")), concat( - "\"$ROOT/target/release/claim_executor\" --required-floor", + "\"$ROOT/target/release/claim_executor\" --required-ci", concat(witness_floor_source_root_flags(), "\n") ) ) diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index fe0f4206108..38ed57f73f5 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -10580,7 +10580,8 @@ fn run() -> Result { // did not run because its input does not exist; saying nothing here would make // an unrun determinism check indistinguishable from a passing one. eprintln!( - "required-ci: phase regen-fixed-point SKIPPED — regen produced no pass-1 digest, so there is nothing to compare a second pass against" + "required-ci: phase regen-fixed-point SKIPPED — regen produced no \ + pass-1 digest, so there is nothing to compare a second pass against" ); phase_failures .push("regen-fixed-point skipped (no pass-1 digest from regen)".to_string()); From 129dc9ab09d5ec488c16b1c97d05424d96bd7982 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 09:19:03 +0000 Subject: [PATCH 3/7] The fabric model still described the two-step job (CI run 32347573121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first CI execution of the composed step found two real defects and named both, which is the mechanism working: `phases_run=4 failed=2`. THIS COMMIT FIXES THE ONE THAT IS MINE. `gunbc.fabric_witness_run` is a second model of the same job shape — the floor's priced demand on the compute fabric — and the consolidation left it describing the ladder I deleted: floor_work_contract steps: ["v1-dag-parse", "required-floor"] two steps floor_run_command argv: [..., "--required-floor"] old mode so `fabric_argv_and_workflow_step_agree_on_source_roots` correctly went red: the two representations no longer agreed. Both re-pointed at the one composed step. THE OLD COMMENT'S CONCERN WAS RIGHT AND IS NOW BETTER SERVED, which is why the row moved rather than the concern being dropped. It read that collapsing the two steps "would make a parse failure and a floor failure indistinguishable in the receipt, which is the distinction gunbc#8466 -> #8519 was paid to learn." The receipt now distinguishes FOUR phases, not two steps, and prints `FAILED PHASE ` per failure — demonstrated by the very run that caught this, which named a regen drift AND a floor failure where a ladder would have surfaced them one merge at a time. WHAT IT COSTS, stated rather than glossed: resumability was per-step, so a green parse could be receipt-satisfied and skipped on rerun. One step means one receipt and the whole run repeats. Real consequence of the consolidation; phase-grain resumability belongs with the cost basis, not here. A GAP FOUND WHILE FIXING IT. The witness is named "argv and workflow step AGREE" but only compared source roots and that the SCRIPT names the mode — it never checked the ARGV names the same mode. So the two could drift on the one flag that decides what runs, and stay green. Found by execution: after re-pointing the script, the argv still said `--required-floor` and this witness passed. It now asserts the argv carries `--required-ci` and does NOT carry `--required-floor`. Mutation-tested with the restore and the re-verification in ONE command, per the lesson from the previous commit: flipping the argv flag turns it false, restoring turns it true, and the restored line is printed. NOT FIXED HERE, because it is not mine: `regen FAIL generated surface drift: v1_compiler_emit_rust.rs`. Main is ALREADY RED with the identical failure at 4cec10f66 (run 32343207326). Bisected to #8614, which changed the authority `src/v1/05_emit_rust.dag` without regenerating its mirror `src/v1/stage0/src/v1_compiler_emit_rust.rs`. My PR inherits it because PR runs check out the merge ref. It is reported separately rather than bundled here. Co-Authored-By: Claude Opus 5 (1M context) --- dag/gunbc/fabric_witness_run.dag | 35 +++++++++++++++++----- dag/test/claim/fabric_witness_run_test.dag | 27 +++++++++++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/dag/gunbc/fabric_witness_run.dag b/dag/gunbc/fabric_witness_run.dag index bf114c7e869..12361d49655 100644 --- a/dag/gunbc/fabric_witness_run.dag +++ b/dag/gunbc/fabric_witness_run.dag @@ -49,17 +49,36 @@ import std.types { String, List } // about where or when it runs -- scheduling facts live in ExecutionRequirements // beside it, so revising a thread estimate cannot mint a new semantic Work. // -// Two steps, not one, because the workflow gates them separately and the v1 -// parse gate is a blocking step in its own right: collapsing them into one step -// would make a parse failure and a floor failure indistinguishable in the -// receipt, which is the distinction gunbc#8466 -> #8519 was paid to learn. - +// ONE STEP, FOUR PHASES (2026-08-20). This read "Two steps, not one, because the +// workflow gates them separately and the v1 parse gate is a blocking step in its +// own right: collapsing them into one step would make a parse failure and a floor +// failure indistinguishable in the receipt, which is the distinction +// gunbc#8466 -> #8519 was paid to learn." +// +// THE CONCERN WAS RIGHT AND IS NOW BETTER SERVED, which is why the row changed +// rather than the concern being dropped. The workflow no longer gates the phases +// separately -- the operator directed that the sequencing move out of the GitHub +// Actions step list and into the binary -- so a contract listing two steps is a +// SECOND, now-false description of a job shape that has one. What made the two +// steps worth having was the receipt's ability to say WHICH part failed, and that +// distinction survives at a finer grain: `--required-ci` reports four phases and +// prints `FAILED PHASE ` per failure, so parse, regen, regen-determinism +// and floor are four distinguishable outcomes where there were two. Measured on +// the first CI run of the composed step (32347573121), which reported +// `phases_run=4 failed=2` and named both -- a regen drift AND a floor failure -- +// where a step ladder would have surfaced them one merge at a time. +// +// WHAT THIS DOES COST, stated rather than glossed: resumability was per-step, so +// a green parse could previously be receipt-satisfied and skipped on a rerun. +// One step means one receipt and the whole run repeats. That is a real +// consequence of the consolidation, not an oversight, and the phase-grain +// resumability that would restore it belongs with the cost basis rather than +// here. fn floor_work_contract(source: SourceManifestRef) -> WorkContract { WorkContract { program: "gunbc.claim_executor", steps: [ - WorkStep { step_id: "v1-dag-parse", resumability: ReceiptSatisfiable }, - WorkStep { step_id: "required-floor", resumability: ReceiptSatisfiable }, + WorkStep { step_id: "required-ci", resumability: ReceiptSatisfiable }, ], source: source, inputs: floor_inputs_manifest_ref(), @@ -137,7 +156,7 @@ fn floor_run_command() -> ArgvCommand { ArgvCommand { argv: fold( witness_layer_roots, - init: ["target/release/claim_executor", "--required-floor"], + init: ["target/release/claim_executor", "--required-ci"], f: fn(acc, root) { append(acc, items: ["--source-root", root]) }, ), } diff --git a/dag/test/claim/fabric_witness_run_test.dag b/dag/test/claim/fabric_witness_run_test.dag index da9fcacdea3..2f75268cec4 100644 --- a/dag/test/claim/fabric_witness_run_test.dag +++ b/dag/test/claim/fabric_witness_run_test.dag @@ -231,10 +231,18 @@ test fn floor_argv_carries_every_declared_source_root() -> Bool { && length(xs: argv) == 2 + 2 * length(xs: witness_layer_roots) } +// AGREEMENT INCLUDES THE MODE, NOT ONLY THE ROOTS. This checked that both +// representations carry every source root and that the script names the mode -- +// but it never checked that the ARGV names the same mode as the script, so the +// two could drift apart on the one flag that decides what actually runs and +// nothing would notice. Found while re-pointing both at `--required-ci`: the +// argv was still `--required-floor` and this witness was green. test fn fabric_argv_and_workflow_step_agree_on_source_roots() -> Bool { let argv = floor_run_command().argv all(witness_layer_roots, r => any(argv, a => a == r)) - && string_contains(s: witness_floor_run_script(), pattern: "--required-floor") + && any(argv, a => a == "--required-ci") + && !any(argv, a => a == "--required-floor") + && string_contains(s: witness_floor_run_script(), pattern: "--required-ci") && all(witness_layer_roots, r => string_contains(s: witness_floor_run_script(), pattern: concat("--source-root \"$ROOT/", concat(r, "\"")))) } @@ -243,10 +251,19 @@ test fn floor_inputs_manifest_ref_folds_the_same_authority() -> Bool { floor_inputs_manifest_ref() == "gunbc.floor.inputs.source-roots:dag+src/v2" } -// The floor and the v1 parse gate are TWO steps, so a receipt can say which one -// failed. That distinction is what gunbc#8466 -> #8519 paid to learn. -test fn floor_contract_keeps_the_parse_gate_as_its_own_step() -> Bool { - length(xs: floor_work_contract(source: t_source()).steps) == 2 +// ONE STEP, AND THE RECEIPT DISTINGUISHES MORE THAN IT USED TO. +// +// This asserted `steps == 2` -- the floor and the v1 parse gate as separate +// steps, so a receipt could say which one failed (gunbc#8466 -> #8519). The +// workflow no longer gates them separately: the phases moved into the binary, so +// the contract describes one step and the receipt names one of FOUR phases +// rather than one of two steps. The claim is not weakened by the change, so the +// witness is re-pointed rather than retired -- what it defends is that the +// contract matches the job shape, and the number is the thing that moved. +test fn floor_contract_is_one_composed_step() -> Bool { + let steps = floor_work_contract(source: t_source()).steps + length(xs: steps) == 1 + && all(steps, s => s.step_id == "required-ci") } // THE FLOOR IS AN ORDINARY PRICED DEMAND. These pin the two properties that, From 189091dc8c5caae3de734ddbd2d01cd31b3e55ca Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 16:33:59 +0000 Subject: [PATCH 4/7] =?UTF-8?q?Stop=20building=20the=20parse=20binary=20CI?= =?UTF-8?q?=20no=20longer=20runs=20=E2=80=94=20and=20unpin=20it=20from=20m?= =?UTF-8?q?y=20witness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by the side thread reviewing #8647 for surplus work. CI still compiled `v1_src_dag_parse` after the consolidation removed the only step that invoked it. THE AUTHORITY ALREADY STATED THE RULE, two lines above the row I left stale: "naming a binary that no step runs buys nothing and costs a compile." The row's own comment said the bin was added for the step below it — the step this PR deleted. So this is not a new principle, it is the consolidation failing to carry its own deletion through to the build list. WORSE, AND THE PART WORTH RECORDING: my consolidation witness ASSERTED the surplus. `w_the_parse_sweep_survives_the_fold` required the yml to contain `--bin v1_src_dag_parse`, using "CI compiles the parse binary" as a proxy for "the parse sweep survives". The two came apart the moment the sweep moved INTO the composed run and the binary stopped being invoked — so the witness was pinning a surplus compile in place as a requirement, which is the opposite of what its name promised. A green witness protecting waste is worse than no witness, because the roster reads as coverage. REPLACED by `w_the_retired_parse_binary_is_no_longer_built`, which asserts what its subject can actually decide: the emitted yml invokes `--required-ci`, builds `claim_executor`, and does NOT build the retired bin. The comment names the boundary explicitly — this file reads emitted workflow text and CANNOT see that the parse phase runs. That is established by execution (`required-ci: parse OK 50 file(s) parse-clean`, run 32371293567), and a static witness claiming it would be asserting something its subject does not contain. THE BINARY STAYS IN THE TREE. Running the parse sweep alone is the cheapest check available while editing src/v1, and it is a thin caller of the same `cli_run` walk rather than a second implementation. What it stops being is CI's business. Mutation-tested: putting the bin back in `witness_floor_required_bins` turns the new witness false; restoring turns it true. The restore was verified by reading the row and the emitted yml directly, not by a symbol count — the timeout ate the in-command re-verify again, which is exactly why the file state is checked explicitly now. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/witnesses.yml | 2 +- dag/gunbc/witness_floor_workflow.dag | 19 ++++++++++++-- ...or_workflow_consolidation_witness_test.dag | 26 ++++++++++++++----- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index ec7d4ce5eee..e549b9fd81c 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -30,7 +30,7 @@ jobs: - name: Build the witness fold id: build_witness_fold run: | - cargo build --release -p v1-compiler --bin claim_executor --bin gunbc --bin v1_src_dag_parse + cargo build --release -p v1-compiler --bin claim_executor --bin gunbc - name: "Required CI: parse, regen, regen determinism, witness floor" run: | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) diff --git a/dag/gunbc/witness_floor_workflow.dag b/dag/gunbc/witness_floor_workflow.dag index 47a8137a002..a7a2f6f9233 100644 --- a/dag/gunbc/witness_floor_workflow.dag +++ b/dag/gunbc/witness_floor_workflow.dag @@ -94,8 +94,23 @@ data witness_floor_concurrency_group: String = "witness-floor-${{ github.event.p // `gunbc` is the CLI the repository's own tooling invokes. The predecessor build job named // nineteen binaries, most of which this cut deleted; naming a binary that no step runs buys // nothing and costs a compile. -// `v1_src_dag_parse` ADDED 2026-08-19 for the step below, which is the only thing that runs it. -data witness_floor_required_bins: List = ["claim_executor", "gunbc", "v1_src_dag_parse"] +// `v1_src_dag_parse` was ADDED 2026-08-19 for a step that ran it, and REMOVED 2026-08-20 with +// that step. The consolidation folded the parse sweep into `--required-ci` as an in-process +// phase calling the same `cli_run` walk, so nothing in this job invokes the standalone binary +// any more -- and the rule stated two lines above is exactly the one its row was breaking: +// naming a binary that no step runs buys nothing and costs a compile. +// +// Caught in review of gunbc#8647 rather than by me, and the way I got it wrong is worth the +// line: the consolidation witness I wrote ASSERTED `--bin v1_src_dag_parse` was present, using +// "CI builds the parse bin" as a proxy for "the parse sweep survives". It is a bad proxy -- +// survival is established by the composed run EXECUTING the phase (`parse OK 50 file(s) +// parse-clean`, run 32371293567), never by compiling a binary nothing runs -- so the witness +// was pinning the surplus in place as a requirement. +// +// The binary itself stays in the tree: running the parse sweep alone is the cheapest check +// available while editing src/v1, and it is a thin caller of the same function, not a second +// implementation. What it no longer is, is CI's business. +data witness_floor_required_bins: List = ["claim_executor", "gunbc"] fn witness_floor_checkout_step() -> Step { UsesStep { diff --git a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag index 2097a490e4b..514d3e33ca3 100644 --- a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag +++ b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag @@ -39,14 +39,26 @@ test fn w_no_phase_precondition_reads_another_phases_outcome() -> Bool { && string_contains(s: yml, pattern: "steps.build_witness_fold.outcome == 'success'") } -// THE PARSE SWEEP STILL HAS AN EXECUTING CONSUMER. Folding a check into another process is only -// legitimate if it still runs; a consolidation that quietly dropped a phase would look exactly -// like a consolidation that kept it, from the step count alone. The binary is built with the -// parse bin still declared, and the composed mode calls the same walk the bin calls. -test fn w_the_parse_sweep_survives_the_fold() -> Bool { +// THE RETIRED BINARY IS NO LONGER BUILT, and this replaces a claim that was quietly wrong. +// +// It read "the parse sweep still has an executing consumer" and asserted the yml contained +// `--bin v1_src_dag_parse`. That used "CI compiles the standalone parse binary" as a proxy for +// "the parse sweep still runs" -- and the two came apart the moment the consolidation landed, +// because the sweep moved INTO the composed run while the binary stopped being invoked at all. +// The witness was therefore pinning a surplus compile in place as a requirement, which is the +// opposite of what it was named for. Found in review of gunbc#8647. +// +// WHAT THIS FILE CAN AND CANNOT DECIDE. The subject here is emitted workflow text, so it can +// see that the retired binary is not built and that the composed mode is invoked. It CANNOT +// see that the parse phase runs -- that lives in the binary and is established by execution +// (`required-ci: parse OK 50 file(s) parse-clean`, run 32371293567). Naming the boundary is +// the point: a static witness that claimed the phase runs would be asserting something its +// subject does not contain. +test fn w_the_retired_parse_binary_is_no_longer_built() -> Bool { let yml = expected_witness_floor_yml() - string_contains(s: yml, pattern: "--bin v1_src_dag_parse") - && string_contains(s: yml, pattern: "--required-ci") + string_contains(s: yml, pattern: "--required-ci") + && string_contains(s: yml, pattern: "--bin claim_executor") + && !string_contains(s: yml, pattern: "--bin v1_src_dag_parse") } // RED CONTROL ON A DIFFERENT AXIS FROM THE INVOCATIONS ABOVE: the retired steps' NAMES. From 3ec13bf0a1934805f42e0e71c41cf48a25708cf6 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 17:20:21 +0000 Subject: [PATCH 5/7] Fix the duplicated memo receipt (review 54101); close the pass-1 dual input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TWO FINDINGS, ONE FROM REVIEW AND ONE FROM THE SIDE THREAD. 1. THE MEMO RECEIPT PRINTED TWICE, and my own comment caused it. The previous commit re-derives `report_required_floor_outcome` from main's inline block on every merge that touches it. Main's block ALREADY carried #8642's memo line — #8642 is merged — and I grafted a second copy on top, so both `--required-floor` and `--required-ci` emitted the receipt twice. That degrades the exact "one receipt, both numbers" property #8642 introduced: two lines reporting one pair is the second-representation shape the receipt existed to remove. The instruction that caused it is deleted with the duplicate. It read "each merge has to graft it back deliberately" — an unconditional re-add with no check for what re-derivation already brought. Re-derivation copies main's block wholesale, so the line arrives WITH it and needs no grafting. The surviving comment now says so, and says that exactly one may exist. 2. THE PASS-1 DIGEST HAD TWO SOURCES AND A SILENT PRECEDENCE RULE. The receipt is read unconditionally — the cross-tree refusal and `PriorReceiptRef` are provenance facts only the file carries — so when a caller ALSO supplies the digest in memory it exists twice, and `pass1_digest.unwrap_or(prior)` silently preferred the argument. A disagreement decided nothing and reported nothing. WHOSE DEFECT IT IS: mine. Until the phases shared a process every caller passed `None`, so the file was the only source and `unwrap_or` had one arm in practice. The composed run is what supplies the argument, so the change creating the second source is the change that closes it. WHAT IT IS NOT, stated because the side thread called it a hard blocker and it is weaker than that: it does not guard an active defect on the composed path. There `run_required_regen` writes the receipt and returns the same digest in one pass, so the two agree BY CONSTRUCTION and the arm is unreachable. I tried to exercise it end-to-end by corrupting the receipt and re-running `--required-ci`, and the test was void — regen rewrites the receipt before the fixed point reads it. The refusal guards the FUNCTION's contract, for a caller supplying a digest against a receipt written by some other run at this commit. That unreachability is why the decision is EXTRACTED as `reconcile_pass1_digest`: reaching the arm through the real function needs a seven-minute emit, and a wall no test can reach is a wall nobody knows works. `pass1_digest_disagreement_refuses_rather_than_preferring_one` asserts the refusal names BOTH values, plus two positive controls (agreeing, and None) without which a function that refused everything would also pass. Mutation-tested with the restore and re-verification in ONE command: disarming the guard makes it FAILED, restoring makes it ok, and the restored source line is counted rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/stage0/src/bin/claim_executor.rs | 21 ++++---- src/v1/stage0/src/required_regen_host.rs | 68 +++++++++++++++++++++++- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index cfc96b6b1d7..afbe1acbb04 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -12014,8 +12014,17 @@ fn report_required_floor_outcome(outcome: &v1_compiler::cli_run::RequiredFloorOu outcome.stale_route_gap.len(), outcome.over_cost_line_diagnostic ); - // One receipt, both numbers. This replaces a per-miss trace line that had no - // hit counterpart, so the ratio it is really about was never readable. + // ONE receipt, both numbers (#8642). This replaced a per-miss trace line that had no hit + // counterpart, so the ratio it is really about was never readable. + // + // EXACTLY ONE OF THESE MAY EXIST, and a duplicate is not cosmetic: two lines reporting one + // pair is the second-representation shape the receipt was introduced to remove, so + // duplicating it degrades the property it asserts. There WAS a second copy here briefly — + // this function is re-derived from main's inline block on every merge that touches it, and + // a note reading "each merge has to graft it back deliberately" instructed the re-add + // without saying to check whether main's block already carried it. It did. Caught in + // review 54101. The instruction is deleted with the duplicate: re-derivation copies main's + // block wholesale, so this line arrives WITH it and needs no grafting. let (memo_hits, memo_misses) = v1_compiler::cli_run::compile_dag_rust_emit_check_memo_counts(); eprintln!( "required-floor: compile_dag_rust_emit_check_memo hits={memo_hits} \ @@ -12061,14 +12070,6 @@ fn report_required_floor_outcome(outcome: &v1_compiler::cli_run::RequiredFloorOu for stale in &outcome.stale_route_gap { eprintln!("required-floor: STALE-ROUTE-GAP {stale}"); } - // One receipt, both numbers (#8642). This replaced a per-miss trace line that had no hit - // counterpart, so the ratio it is really about was never readable. It reached main INSIDE - // the inline block this function replaced, so each merge has to graft it back deliberately. - let (memo_hits, memo_misses) = v1_compiler::cli_run::compile_dag_rust_emit_check_memo_counts(); - eprintln!( - "required-floor: compile_dag_rust_emit_check_memo hits={memo_hits} \ - misses={memo_misses}" - ); } /// Whether the floor outcome permits a green run. diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 4d258c4ad73..8b647c27277 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -275,6 +275,41 @@ pub fn run_required_regen( Ok(RequiredRegenOutcome { receipt, failures }) } +/// Reconcile the two available answers to "what did the first generation emit". +/// +/// TWO SOURCES FOR ONE FACT, RECONCILED BY REFUSAL RATHER THAN BY PRECEDENCE. The receipt file +/// is read unconditionally — the cross-tree refusal and the `PriorReceiptRef` are provenance +/// facts only the file carries — so whenever a caller ALSO supplies the digest in memory it +/// exists twice. The previous form was `pass1_digest.unwrap_or(prior)`, which silently preferred +/// the argument: two representations of one fact with a precedence rule, so a disagreement +/// decided nothing and reported nothing (DESIGN §3). +/// +/// WHO MADE IT REACHABLE, stated because it changes whose defect this is: until the phases +/// shared a process every caller passed `None`, so the file was the only source. The composed +/// `--required-ci` run is what supplies the argument, so the change that creates the second +/// source is the change that closes it. +/// +/// AND WHAT IT IS *NOT*: this does not guard an active defect on the composed path. There, +/// `run_required_regen` writes the receipt and returns the same digest in one pass, so the two +/// agree by construction and this arm is unreachable. It guards the FUNCTION's contract, for a +/// caller that supplies a digest against a receipt written by some other run at this commit — +/// a rebuild between passes, a mutated `target/`, or a first pass that refused after writing. +/// Extracted from the call site precisely so that claim can be tested without running a +/// seven-minute emit to reach it. +fn reconcile_pass1_digest(supplied: Option, prior: &str) -> Result { + match supplied { + Some(supplied) if supplied != prior => Err(format!( + "refusal: pass-1 digest disagreement — the caller supplied {supplied} but the \ + receipt at this commit records {prior}. These are two answers to what the first \ + generation emitted; the fixed-point comparison is meaningless until they agree. \ + Re-run `claim_executor --required-regen` at this commit so the receipt and the \ + in-memory pass agree." + )), + Some(supplied) => Ok(supplied), + None => Ok(prior.to_string()), + } +} + pub fn run_required_regen_fixed_point( receipt_rel: &str, pass1_digest: Option, @@ -307,7 +342,7 @@ pub fn run_required_regen_fixed_point( )); } - let pass1 = pass1_digest.unwrap_or(prior.candidate_generated_digest); + let pass1 = reconcile_pass1_digest(pass1_digest, &prior.candidate_generated_digest)?; let sources = super::regen_input_sources(&workspace)?; let authority_digest = authority_digest_from_sources(&sources)?; let emitted = compile_stage0(&workspace)?; @@ -960,4 +995,35 @@ mod tests { verify_candidate_tree(&src, &["foo.rs".to_string()]).expect("candidate present"); let _ = fs::remove_dir_all(&tmp); } + + // ENROLLED RED FOR THE DUAL-INPUT REFUSAL. The arm it guards is UNREACHABLE from the + // composed `--required-ci` path — there `run_required_regen` writes the receipt and returns + // the same digest in one pass, so the two agree by construction — which is exactly why the + // decision was extracted from its call site: reaching it through the real function would + // require a seven-minute emit, and a wall no test can reach is a wall nobody knows works. + // + // RED: restoring `pass1_digest.unwrap_or(prior)` makes the disagreement case return Ok and + // fails the first assertion. The None and agreeing cases are the positive controls, without + // which a function that refused everything would also pass. + #[test] + fn pass1_digest_disagreement_refuses_rather_than_preferring_one() { + let err = reconcile_pass1_digest(Some("supplied-abc".to_string()), "receipt-xyz") + .expect_err("two different answers to one fact must refuse"); + assert!( + err.contains("supplied-abc") && err.contains("receipt-xyz"), + "the refusal must name BOTH values so the reader sees a contradiction rather than \ + a comparison whose operand was chosen for them: {err}" + ); + + assert_eq!( + reconcile_pass1_digest(Some("same".to_string()), "same").expect("agreement is fine"), + "same", + "positive control: agreeing sources are not a refusal" + ); + assert_eq!( + reconcile_pass1_digest(None, "from-receipt").expect("no second source, no conflict"), + "from-receipt", + "positive control: with one source the receipt is simply used" + ); + } } From 6d2d80613890f38a2f168aa8cd24df7bd31cf60b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 17:45:09 +0000 Subject: [PATCH 6/7] Baseline the floor heartbeat's cpu_ms, as wall_s already was Composing the CI phases into one process changed what a process-cumulative counter means. floor_resource_sample() read /proc/self/stat utime+stime absolutely, so regen's multi-threaded compile now landed on the floor's line: the floor's FIRST heartbeat reported cpu_ms=59830 with its own process (run 32341236470) and cpu_ms=786650 without one (run 32371293567). wall_s was already relative to heartbeat spawn; cpu_ms now is too. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/stage0/src/cli_run.rs | 38 +++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 655ebf1fcf8..559f2c37f2c 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -40039,7 +40039,35 @@ static FLOOR_SEAM: std::sync::Mutex = std::sync::Mutex::new(String::new( // and major faults rising while progress is flat is reclaim churn. One /proc read per tick buys // that discrimination, and without it a heartbeat only proves the process is alive -- which the // runner's "Terminate orphan process" line already proved, after four hours. -fn floor_resource_sample() -> String { +/// Process CPU milliseconds, cumulative since process start. Read directly only to establish +/// a baseline; the heartbeat reports a DELTA against one. +fn process_cpu_ms() -> u64 { + let stat = std::fs::read_to_string("/proc/self/stat").unwrap_or_default(); + let f: Vec<&str> = stat + .rsplit(')') + .next() + .unwrap_or("") + .split_whitespace() + .collect(); + let tick = |i: usize| f.get(i).and_then(|v| v.parse::().ok()).unwrap_or(0); + (tick(11) + tick(12)) * 1000 / 100 +} + +/// `cpu_baseline_ms` is subtracted from the process-cumulative CPU so the reported figure is +/// THIS phase's, matching `wall_s`, which has always been relative to heartbeat spawn. +/// +/// WHY IT NEEDED A BASELINE AT ALL. Until the CI phases shared a process, the floor ran in a +/// process of its own and cumulative-since-process-start and since-the-floor-started were the +/// same number. The composed `--required-ci` run puts regen's multi-threaded `compile_stage0` +/// in front of it, and the difference is not marginal: measured across two CI runs of the same +/// corpus, the floor's FIRST heartbeat reported cpu_ms=59830 when it had its own process +/// (32341236470) and cpu_ms=786650 when it did not (32371293567) — 787 CPU-seconds of someone +/// else's work, on the floor's line, at the floor's first beat. +/// +/// That is the attribution failure this PR's own thesis is about, introduced by this PR, caught +/// by comparing the two runs rather than by reasoning about the code. `wall_s` was already +/// right; only the CPU counter was absolute. +fn floor_resource_sample(cpu_baseline_ms: u64) -> String { let stat = std::fs::read_to_string("/proc/self/stat").unwrap_or_default(); let f: Vec<&str> = stat .rsplit(')') @@ -40050,7 +40078,7 @@ fn floor_resource_sample() -> String { // Fields are indexed from the field AFTER comm: utime/stime are 12/13 here, majflt is 10. let tick = |i: usize| f.get(i).and_then(|v| v.parse::().ok()).unwrap_or(0); let hz = 100u64; - let cpu_ms = (tick(11) + tick(12)) * 1000 / hz; + let cpu_ms = ((tick(11) + tick(12)) * 1000 / hz).saturating_sub(cpu_baseline_ms); let majflt = tick(9); let rss_kb = std::fs::read_to_string("/proc/self/statm") .ok() @@ -40319,6 +40347,10 @@ fn floor_value_constructor(v: &v1_interpreter::Value) -> &'static str { // slow, which is the property that makes it useful. fn spawn_floor_heartbeat() { let started = std::time::Instant::now(); + // Taken HERE, beside the wall baseline, so both counters answer "since the floor started" + // rather than one of them answering "since the process started". See the note on + // `floor_resource_sample` for the measurement that made this necessary. + let cpu_baseline_ms = process_cpu_ms(); // 60s is the CI cadence: dense enough to bound a phase, sparse enough not to bloat a // job log. It is too coarse to LOCALISE anything — a 2.7 GB step between two samples // names a minute, not a cause — so a local investigation can tighten it. Bounded below @@ -40351,7 +40383,7 @@ fn spawn_floor_heartbeat() { "[floor-heartbeat] wall_s={} phase={} {}", started.elapsed().as_secs(), if seam.is_empty() { "" } else { &seam }, - floor_resource_sample() + floor_resource_sample(cpu_baseline_ms) ); beat += 1; if beat % 10 == 0 { From 074e8459cb04e670f5bcf1b0784cd54edc1bfe9f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 20 Aug 2026 18:12:37 +0000 Subject: [PATCH 7/7] Refusal has no digest to hand the fixed point (review finding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A population refusal returns Ok with a receipt whose digest fields hold the sentinel `refused:population` — the receipt's fields are String and there is nowhere else to put "there was no measurement". The composed coordinator read that receipt, so a refusal handed the sentinel to phase three, which compared it against a real pass-two digest and reported fixed-point refused: pass-1 digest refused:population != pass-2 digest a determinism failure nobody measured, wearing the shape of a real one (§5 fabricated plausible output). The sentinel was documented as known residue; what was missed is that consolidation gave it a route out. RequiredRegenOutcome now carries FirstGeneration = Measured(digest) | NotMeasured(reason), and pass1_digest_for_fixed_point is the only route to the digest — a refusal has no digest field to read, so phase three reports its existing SKIPPED state. Drift still runs the fixed point; drift and refusal were never the same thing. Three premise-accuracy edits from the same review: FIVE CAUSES -> SEVEN (main added route_gap and stale_route_gap and the sentence kept saying five); the dual-input control said ENROLLED RED when the Rust suite has been out of CI since 2026-07-11, so it says LOCAL; and the workflow witness said "the retired parse binary is no longer built" when fleet-converge still builds it — scoped to the required workflow, which is what its subject can decide. Co-Authored-By: Claude Opus 5 (1M context) --- ...or_workflow_consolidation_witness_test.dag | 8 +- src/v1/stage0/src/bin/claim_executor.rs | 16 ++- src/v1/stage0/src/cli_run.rs | 2 + src/v1/stage0/src/required_regen_host.rs | 119 ++++++++++++++++-- 4 files changed, 130 insertions(+), 15 deletions(-) diff --git a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag index 514d3e33ca3..ff13fa4512f 100644 --- a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag +++ b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag @@ -39,7 +39,11 @@ test fn w_no_phase_precondition_reads_another_phases_outcome() -> Bool { && string_contains(s: yml, pattern: "steps.build_witness_fold.outcome == 'success'") } -// THE RETIRED BINARY IS NO LONGER BUILT, and this replaces a claim that was quietly wrong. +// THE REQUIRED WORKFLOW NO LONGER BUILDS THE PARSE BINARY -- scoped to this workflow, because +// fleet-converge.yml still builds it from witness_declared_release_bins and a heading reading +// "no longer built" would be false of the repository while true of its subject. +// +// This replaces a claim that was quietly wrong. // // It read "the parse sweep still has an executing consumer" and asserted the yml contained // `--bin v1_src_dag_parse`. That used "CI compiles the standalone parse binary" as a proxy for @@ -54,7 +58,7 @@ test fn w_no_phase_precondition_reads_another_phases_outcome() -> Bool { // (`required-ci: parse OK 50 file(s) parse-clean`, run 32371293567). Naming the boundary is // the point: a static witness that claimed the phase runs would be asserting something its // subject does not contain. -test fn w_the_retired_parse_binary_is_no_longer_built() -> Bool { +test fn w_the_required_workflow_does_not_build_the_parse_binary() -> Bool { let yml = expected_witness_floor_yml() string_contains(s: yml, pattern: "--required-ci") && string_contains(s: yml, pattern: "--bin claim_executor") diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index afbe1acbb04..273da55330b 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -10580,7 +10580,15 @@ fn run() -> Result { // emitter reproduces itself is a SEPARATE question from whether it matches what // is committed. Skipping the fixed point on a regen mismatch would conflate // them and lose a determinism signal exactly when drift makes it interesting. - Some(outcome.receipt.candidate_generated_digest().to_string()) + // + // DRIFT AND REFUSAL ARE DIFFERENT, though, and the receipt cannot tell them + // apart -- a population refusal writes the sentinel `refused:population` into + // the same `String` field a real digest occupies. Reading the receipt here would + // hand that sentinel to phase three, which would dutifully compare it against a + // real pass-two digest and report a determinism failure nobody measured. So the + // digest comes from the outcome's typed `FirstGeneration`, where a refusal has + // no digest field to read. + v1_compiler::cli_run::pass1_digest_for_fixed_point(&outcome).map(str::to_string) } Err(e) => { // A REFUSAL IS NOT A MISMATCH. Nothing was emitted, so there is no pass-1 @@ -12074,8 +12082,10 @@ fn report_required_floor_outcome(outcome: &v1_compiler::cli_run::RequiredFloorOu /// Whether the floor outcome permits a green run. /// -/// FIVE CAUSES, ONE STOPPED LINE — and the conjunction is written once here rather than at each -/// caller, because a mode that forgot one of the five would green a run the other refused. +/// SEVEN CAUSES, ONE STOPPED LINE — and the conjunction is written once here rather than at each +/// caller, because a mode that forgot one of them would green a run the other refused. (The +/// count is stated because a reader checks it; it was five before main added `route_gap` and +/// `stale_route_gap`, and the sentence went on saying five through the merge that added them.) fn required_floor_outcome_is_clean(outcome: &v1_compiler::cli_run::RequiredFloorOutcome) -> bool { outcome.failures.is_empty() && outcome.stale_quarantine.is_empty() diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 559f2c37f2c..ac00a90c90e 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -42374,6 +42374,8 @@ fn witness_eval_verdict_from_claim_outcome( } } +pub use required_regen_host::{pass1_digest_for_fixed_point, FirstGeneration}; + pub fn run_required_regen( candidate_dir_rel: &str, receipt_rel: &str, diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 8b647c27277..3295a02bb44 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -171,6 +171,33 @@ impl RegenReceipt { pub struct RequiredRegenOutcome { pub receipt: RegenReceipt, pub failures: Vec, + /// WHETHER PASS ONE ACTUALLY EMITTED, kept OFF the receipt's digest fields on purpose. + /// + /// A population refusal happens before any content comparison, so there is no first + /// generation to have a digest OF. The receipt still has to carry a `String` in that + /// position, and it carries the sentinel `refused:population` — which is why the fixed-point + /// handoff must not read the receipt. Reading it there would compare a real pass-two digest + /// against sentinel prose and report a determinism failure nobody measured: a fabricated + /// plausible output (DESIGN §5), and a convincing one, since the message names two digests + /// and looks exactly like a genuine mismatch. + pub first_generation: FirstGeneration, +} + +/// What pass one produced, as a coproduct rather than as a string that might be a digest. +/// `NotMeasured` has no digest field at all, so the sentinel has no route into a comparison. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FirstGeneration { + Measured(String), + NotMeasured(String), +} + +/// The ONLY way the fixed-point phase learns pass one's digest. `NotMeasured` yields `None`, and +/// phase three reports its own SKIPPED state rather than a refusal it did not observe. +pub fn pass1_digest_for_fixed_point(outcome: &RequiredRegenOutcome) -> Option<&str> { + match &outcome.first_generation { + FirstGeneration::Measured(d) => Some(d.as_str()), + FirstGeneration::NotMeasured(_) => None, + } } pub fn run_required_regen( @@ -241,6 +268,7 @@ pub fn run_required_regen( // `fixed_point_equal: false`, which was not a measurement at all -- the first pass never asks // that question, so a literal `false` asserted a negative answer where the honest content was // "not asked". The variant has no such field, so the placeholder is now unwritable. + let first_generation = FirstGeneration::Measured(candidate_digest.clone()); let receipt = RegenReceipt::FirstGeneration { schema: RECEIPT_SCHEMA.to_string(), commit_sha, @@ -272,7 +300,11 @@ pub fn run_required_regen( emitted_basenames.len() ); - Ok(RequiredRegenOutcome { receipt, failures }) + Ok(RequiredRegenOutcome { + receipt, + failures, + first_generation, + }) } /// Reconcile the two available answers to "what did the first generation emit". @@ -386,7 +418,15 @@ pub fn run_required_regen_fixed_point( )] }; - Ok(RequiredRegenOutcome { receipt, failures }) + // This outcome IS the fixed-point pass; it is not anybody's first generation, and saying so + // is more useful than echoing a digest a later reader might hand onward. + Ok(RequiredRegenOutcome { + receipt, + failures, + first_generation: FirstGeneration::NotMeasured( + "this outcome is the fixed-point pass, not a first generation".to_string(), + ), + }) } struct SyncReport { @@ -546,12 +586,11 @@ fn regen_refusal_outcome( ) -> Result { let receipt_path = workspace.join(receipt_rel); // A population refusal happens BEFORE any content comparison, so the digests are not - // "refused" values of a measurement -- there was no measurement. The sentinel string is - // retained rather than improved because the honest repair is a refusal variant that carries - // no digest fields at all, and that is a wider change than the impersonation this commit - // closes. Named here so it is a known residue rather than something a later reader discovers - // and mistakes for a measured digest: `first_generation_equal: false` below is likewise "not - // asked", not "asked and answered no". + // "refused" values of a measurement -- there was no measurement. The sentinel survives in the + // RECEIPT, whose fields are `String`, and `first_generation_equal: false` below is likewise + // "not asked" rather than "asked and answered no". What no longer survives is the sentinel's + // route OUT: the outcome carries `FirstGeneration::NotMeasured`, so the composed coordinator + // cannot hand this string to the fixed-point phase. let receipt = RegenReceipt::FirstGeneration { schema: RECEIPT_SCHEMA.to_string(), commit_sha, @@ -565,7 +604,8 @@ fn regen_refusal_outcome( write_receipt(&receipt_path, &receipt)?; Ok(RequiredRegenOutcome { receipt, - failures: vec![reason], + failures: vec![reason.clone()], + first_generation: FirstGeneration::NotMeasured(reason), }) } @@ -996,7 +1036,66 @@ mod tests { let _ = fs::remove_dir_all(&tmp); } - // ENROLLED RED FOR THE DUAL-INPUT REFUSAL. The arm it guards is UNREACHABLE from the + // THE SENTINEL HAS NO ROUTE TO THE FIXED-POINT PHASE. + // + // The defect this pins, found in review of gunbc#8647: a population refusal returns `Ok` + // with a receipt whose digest fields hold `refused:population`. Before the typed + // `FirstGeneration`, the composed coordinator read the receipt, so a refusal handed that + // string to phase three, which compared it against a real pass-two digest and reported + // `fixed-point refused: pass-1 digest refused:population != pass-2 digest ` -- a + // determinism failure nobody measured, wearing the exact shape of a real one. + // + // RED, stated at what this test can actually reach: making `pass1_digest_for_fixed_point` + // answer from the receipt -- `Some(outcome.receipt.candidate_generated_digest())` -- fails + // it. What it does NOT reach is the coordinator choosing to bypass the accessor and read the + // receipt itself; that is one call site in `claim_executor.rs`, guarded by the comment there + // and by review, not by this test. Naming the gap rather than implying the test closes it. + #[test] + fn a_refused_first_generation_hands_no_digest_to_the_fixed_point() { + let sentinel_receipt = || RegenReceipt::FirstGeneration { + schema: RECEIPT_SCHEMA.to_string(), + commit_sha: "sha".to_string(), + authority_digest: "auth".to_string(), + committed_generated_digest: "refused:population".to_string(), + candidate_generated_digest: "refused:population".to_string(), + first_generation_equal: false, + changed_paths: Vec::new(), + candidate_artifact: "cand".to_string(), + }; + + let refused = RequiredRegenOutcome { + receipt: sentinel_receipt(), + failures: vec!["refusal: emit produced zero files".to_string()], + first_generation: FirstGeneration::NotMeasured( + "refusal: emit produced zero files".to_string(), + ), + }; + assert_eq!(pass1_digest_for_fixed_point(&refused), None); + // And the sentinel IS still sitting in the receipt, which is what makes the coproduct + // load-bearing rather than decorative: the wrong answer is right there to be read. + assert_eq!( + refused.receipt.candidate_generated_digest(), + "refused:population" + ); + + // POSITIVE CONTROL: ordinary drift is not a refusal. Pass one emitted, the comparison + // disagreed, and the fixed point still has a subject -- skipping it there would lose a + // determinism signal exactly when drift makes it interesting. + let drifted = RequiredRegenOutcome { + receipt: sentinel_receipt(), + failures: vec!["17 file(s) drifted".to_string()], + first_generation: FirstGeneration::Measured("real-digest".to_string()), + }; + assert_eq!(pass1_digest_for_fixed_point(&drifted), Some("real-digest")); + } + + // LOCAL RUST RED CONTROL FOR THE DUAL-INPUT REFUSAL — local, NOT enrolled. The Rust suite + // has been out of CI since the 2026-07-11 operator ruling, so this executes for whoever runs + // it and for no gate. Said plainly because the previous heading claimed "ENROLLED", which is + // the rung inflation DESIGN §4b calls worse than sitting low: an unenrolled control that + // says it is enrolled never ranks for enrolling. + // + // The arm it guards is UNREACHABLE from the // composed `--required-ci` path — there `run_required_regen` writes the receipt and returns // the same digest in one pass, so the two agree by construction — which is exactly why the // decision was extracted from its call site: reaching it through the real function would