Skip to content

Required CI: the measuring process adjudicates the receipt it writes, so a refused phase reaches the exit - #11861

Merged
briansrls merged 7 commits into
mainfrom
session/still-crane-198
Sep 22, 2026
Merged

briansrls merged 7 commits into
mainfrom
session/still-crane-198

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

What

claim_executor --required-ci maps phase outcomes to a process exit in two places. Without --measurement-receipt it refuses on a non-empty phase-failure set. With the flag it wrote the receipt and returned ExitCode::SUCCESS unconditionally — because the D0 design it was written for published the receipt and let a separate step adjudicate it.

So the lane printed a complete, typed, located refusal and then answered success:

required-ci: floor refused: REQUIRED-FLOOR REFUSAL cause=ChangedWitnessObservationFailed ...
required-ci: lane=witnesses phases_run=2 phases_failed=1
required-ci: FAILED PHASE floor refused: ...

…with the step and the job both conclusion=success. Verified against the GitHub jobs API, not only the log: run 35503853026 (head cb39f95, floor job 106060214538) and run 35510547600 (head e78927a, floor job 106077573881).

The refusal itself is correct and is untouched. When a changed kernel file such as dag/std/types.dag makes the base side impossible to reconstruct, the changed-witness sublane declines to plan rather than widening to everything or narrowing to nothing. What was wrong is only the exit status and what the PR view was told.

The two repairs

1. The write is not the verdict. The measuring process now adjudicates the file it just wrote and returns that verdict, reaching the exit through the same function --adjudicate-measurement-receipt uses. One relation, one exit. Readback, JSON transport and version agreement are all inside the verdict, so a receipt that serialized its blockers away is a refusal rather than a pass.

2. A second leak in the same branch, found by the audit the first one forced. The blocker set written into the receipt was synthesized from the phase-failure list by comparing a blocker's phase to the whole failure sentence, and the failure spelled exactly floor was exempted by name. So a floor reporting not-clean while yielding no blockers of its own produced a MeasurementCompleted receipt with an empty blocker set — a failed phase with nothing to adjudicate, i.e. a green built by construction. The test is now phase word against phase word, and no phase is exempt.

Audit of the lane's other refusal arms: all 22 appends to phase_failures (lane-roster, parse, generated-artifact stage0-mirrors / docs-projections / carrier / unadjudicated, regen-fixed-point incl. its unmeasured and refused arms, dag-artifact-identity incl. subject-unobtainable, floor incl. floor-refused, lane-roster ran-set, source-root ingest receipt) now reach the blocker set, and the blocker set is what the exit is derived from. One arm is deliberately not a phase and stays as it is: --measurement-unreached-receipt seals a MeasurementUnreached standing for a separate adjudicator to read, and that standing is typed non-admitted — it says so in the receipt rather than by an exit.

Compatibility

This is the seed-side half and it depends on neither #11829 nor #11836; it is correct under either. It takes no position on which should land.

Evidence

  • Discriminating red + green control, executing the relation rather than asserting a string, in v1_compiler::cli_run::required_ci_measurement. Six tests; the pair that matters is a completed measurement carrying one blocker is never admitted and a clean one is, each also run through the write and the readback. Mutation-verified: forcing the blocker arm to return Admitted reds 2 of the 6 and leaves the green controls green.
  • Route: cargo test --release -p v1-compiler --lib (gunbc.repo_self_build repo_self_test_command) — 6 passed. This is why the receipt types moved out of the binary: gunbc.rung_drop rust_unit_tests_off_the_merge_path already declares that no CI step runs it, and a #[cfg(test)] module beside the binary has no documented route at all. The rung is stated honestly in the failure-mode row rather than claimed at the merge path.
  • cargo clippy --all-targets -p v1-compiler -- -D warnings: clean. cargo fmt --all --check: clean.

The discriminating control, on real CI

Not planned — it arrived while this PR sat red, and it is the comparison the unit controls cannot make.

corpus phases step conclusion
without this change — merge_group job 106103511181 (run 35520456015, queue branch for pr-11769) same broken parse, 13× source annotation sits inside a declaration body phases_run=2 phases_failed=2 success
with this change — job 106102498242 (run 35520066541, head 9ae301c) same phases_run=2 phases_failed=2 failure

It then reproduced on a second, unrelated cause. After #11875 repaired the annotations, parse went clean (6455 file(s) parse-clean) and the declarations sub-check began refusing two different things that had landed on main (IMPORT-MEMBER-ABSENT in dag/test/claim/machine_intake/jade_first_contact_model_witness_test.dag, from #11758; LENS-AUTHORSHIP-ABSENT in src/v2/lens/reference_derived_residency_reading.dag, from #11740). Queue floor job 106184155659 (run 35550476069, queue branch for pr-11676) carried both diagnostics and concluded success; job 106184184120 (run 35550463080, head 5e1a5035a44), this branch with the same two diagnostics, concluded failure.

And the green control arrived too. On head 46a4a6306f2, once main was clean, run 35645340055 floor job 106484305229 passed in 39m31s with the lane exiting 0 — so the pair is complete on live CI: a refused phase reds (twice, on two unrelated corpora), and a clean lane greens.

Same corpus, same refused phases, opposite verdicts. That is a real red and a real green of the mapping on live CI — which no unit test in this repository could have produced, because the thing under test is a process exit read by a CI job.

Two consequences worth stating plainly:

  • This PR is red because it is correct. Its own floor fold is FloorClean (planned=401 executed=401 claims_failed=0); it refuses because main's parse phase is refusing. It cannot go green until main's parse is repaired, and that repair is not this PR's to author — it is open separately as Main repair: hoist three in-body annotations to their module items (parse refuses fleet-wide) #11875, after which this branch takes one merge and one re-run.
  • The defect is live on the landing path, not only the PR path. The last four completed merge_group runs are all success, so commits are being admitted to main by a required gate that declined every phase it ran. That is a plausible mechanism for main having broken twice in one afternoon: the breakages land green.

An earlier draft of this section claimed a red on this PR as evidence the fix worked. That was wrong when written — the only comparison available then failed identically with and without the change — and it is replaced by the table above rather than quietly dropped.

Blind window, stated as what the emission shows

--measurement-receipt appears zero times in the workflow emitted at 85c1356300c (#11742). gunbc#11791 (merged 2026-09-20 05:18 UTC) restored a floor job carrying the receipt-bearing command into a workflow with no adjudicating step. So the window opens there and not earlier; within it, a required run whose floor phase refused reported green with its changed-witness observation never made. Two such runs are named above. No claim is made about how many others fell in it.

Ledger

dag/gunbc/recurring_failure_mode/gate_reported_success_on_a_phase_it_refused.dag — the general class (a gate reporting success on a phase it refused), with both runs as receipts, rung found at mitigatable, rung now mechanically preventable with the honesty bound stated, ceiling structurally impossible, and a next-rung trigger naming the capability (the lane's phase-outcome-to-exit decision as a .dag fold returning std.process ProcessExit). It is bounded explicitly against #11836's measure_mode_command_emitted_into_a_lane_with_no_adjudicator rather than duplicating it: that class is about an emission and its wall is at the emitter; this one is about what the instrument returns when nothing adjudicates.

🤖 Generated with Claude Code

… so a refused phase reaches the exit

claim_executor --required-ci maps phase outcomes to a process exit in two places.
Without --measurement-receipt it refuses on a non-empty phase-failure set. WITH the
flag it wrote the receipt and returned ExitCode::SUCCESS unconditionally, because the
D0 design it was written for published the receipt and let a separate step adjudicate
it. gunbc#11791 restored a floor job carrying that receipt-bearing command into a
workflow with no adjudicating step, so the measuring process became the only authority
left standing on a route where it had been built to decide nothing: runs 35503853026
and 35510547600 both printed `FAILED PHASE floor refused: ... ChangedWitnessObservationFailed`
and concluded success.

The refusal itself is correct and is untouched. What changes is only the exit status:
the measuring process now adjudicates the FILE it just wrote -- readback, JSON transport
and version agreement all inside the verdict -- and reaches the exit through the same
function --adjudicate-measurement-receipt uses. One relation, one exit.

A second leak in the same branch, found by the audit the first one forced: the blocker
set written into the receipt was synthesized by comparing a blocker's PHASE to the whole
failure SENTENCE, and the failure spelled exactly `floor` was exempted by name, so a
floor reporting not-clean while yielding no blockers of its own produced a COMPLETED
measurement with an EMPTY blocker set. Phase word against phase word now, no exemption.

The receipt types and the admission relation move to v1_compiler::cli_run::required_ci_measurement
so the relation has an executing discriminating red -- a completed measurement carrying
one blocker must never be admitted, run through the write and the readback rather than
asserted against a string -- plus its green control, on the one Rust route the repository
documents (`cargo test --release -p v1-compiler --lib`).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title Required CI lane exits 0 on a refused phase: the gate reports green on a declined observation Required CI: the measuring process adjudicates the receipt it writes, so a refused phase reaches the exit Sep 20, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 20, 2026 14:51
gunbai-bot Bot pushed a commit that referenced this pull request Sep 20, 2026
…an losing them

The convergence lane is winding down so the fleet can prioritise v1 performance
and v2 migration. Five subjects it found are unfinished and none of them has a
home outside a session transcript, so they are declared here with the
measurements that produced them rather than a summary of them.

- claim-entry-resolution-population: 8 claim entries cannot resolve for a
  closure gap and 20 more cannot typecheck at all, from 17 sites, EIGHT of them
  in production modules including dag/std/algebra.dag. The floor is green over
  all of them, because an entry that fails to resolve is indistinguishable there
  from one never selected. The counts are a partial census (512 of 1582, sorted
  prefix) and the row says so rather than extrapolating a rate.
- compiler-import-list-binds: the flip control takes the subject from 41
  registry-row-absent causes to 585 modules with one host-loader predicate
  removed; the alternative widen costs 358442 -> 586388 closure rows, moves 1502
  of 1582 entries and charges every already-correct entry. 646 files are
  bare-eligible, which is the transition scope. The fork is the operator's, so
  the row carries both costs rather than a preference.
- boot-probe-must-not-read-a-bare-124-as-a-deadline: executed on coreutils 9.7,
  timeout forwards a child's own 124 (6ms, no expiry) and returns 124 on expiry
  (111ms), so boot_probe_verdict's bare-status reading renders a deadline
  sentence for an ordinary VMM failure. Contained only by its consumer refusing
  both verdicts.
- fleet-srv2-retirement-completion and fleet-assessment-build-provenance-join:
  what remains of srv2 -- seven local slot trees, two live registrations
  (251463, 251484; 91529 and 91513 are already absent by per-ID read), the
  plan/apply spine reaching a target that is not the executor, graceful
  retirement, and the provenance join the assessment consumes as a blocker.

NOT filed here: the required lane concluding success over its own refused phase.
#11857 already declares that subject as required-lane-exit-on-refused-phase and
#11861 implements it, so a second row would be the DESIGN section 3 fork.

The rows carry receipts, not adjectives: each names the run, the command or the
measured pair that established it, so the next lane re-derives rather than
re-discovers. ROADMAP.md is a projection of this authority and regenerates from
it; the host is too loaded to run that regeneration here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Operator wind-down (2026-09-20): step back to v1 performance and v2 migration;
record progress and remaining items as project items.

- action-use-yaml-reader-contract: the bounded-subset YAML contract and the
  deletion of the action-use line projection (#11730, held).
- required-lane-exit-on-refused-phase: the required lane exits 0 on a refused
  phase, with runs 35503853026 and 35510547600 as receipts (#11861).
- workflow-census-claim-budget-margin: cut the reader's per-entry cost so the
  census is not one unrelated workflow edit from red.
- action-runtime-epoch-observation: nothing reads the epoch date against a clock,
  and a runner-host override is unobserved.
- namespace-relocation-batch-grain: one relocation should not cost one admission
  file per consuming declaration.

ROADMAP.md regenerated through gunbc.roadmap_authority expected_roadmap_md.

HISTORY NOTE: this branch is rebuilt on origin/main. The previous head carried a
merge made with -s ours, which recorded main as merged while keeping this side of
every file -- silently reverting main's changes in 17 unrelated files. That merge
is discarded rather than fixed forward, so nothing of main's is lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Operator wind-down (2026-09-20): step back to v1 performance and v2 migration;
record progress and remaining items as project items.

- action-use-yaml-reader-contract: the bounded-subset YAML contract and the
  deletion of the action-use line projection (#11730, held).
- required-lane-exit-on-refused-phase: the required lane exits 0 on a refused
  phase, with runs 35503853026 and 35510547600 as receipts (#11861).
- workflow-census-claim-budget-margin: cut the reader's per-entry cost so the
  census is not one unrelated workflow edit from red.
- action-runtime-epoch-observation: nothing reads the epoch date against a clock,
  and a runner-host override is unobserved.
- namespace-relocation-batch-grain: one relocation should not cost one admission
  file per consuming declaration.

Rebuilt on current main (other lanes keep appending nodes at the same point);
ROADMAP.md regenerated through gunbc.roadmap_authority expected_roadmap_md.
Verified like for like: main 51 PASS, mine 51 PASS, identical FAIL set, 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Operator wind-down (2026-09-20): step back to v1 performance and v2 migration;
record progress and remaining items as project items.

- action-use-yaml-reader-contract: the bounded-subset YAML contract and the
  deletion of the action-use line projection (#11730, held).
- required-lane-exit-on-refused-phase: the required lane exits 0 on a refused
  phase, with runs 35503853026 and 35510547600 as receipts (#11861).
- workflow-census-claim-budget-margin: cut the reader's per-entry cost so the
  census is not one unrelated workflow edit from red.
- action-runtime-epoch-observation: nothing reads the epoch date against a clock,
  and a runner-host override is unobserved.
- namespace-relocation-batch-grain: one relocation should not cost one admission
  file per consuming declaration.

Rebuilt on current main again (sixth time: other lanes keep appending nodes at
the same point). The five rows are byte-identical to the head verified at
d5dc91b -- main 51 PASS, mine 51 PASS, identical FAIL set, 0 errors --
and ROADMAP.md is regenerated on this head through expected_roadmap_md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 21, 2026
…fabric storage wire witness (#11967)

`dag/test/claim/fabric/fabric_storage_wire_witness_test.dag` carried three `//`
blocks INSIDE the body of
`a_presented_login_crosses_the_wire_as_that_login_and_absent_as_absent`.
DESIGN §4c admits only standalone leading blocks attached to a module-scope
declaration, so the file refuses and, because one witness compile error refuses
the whole floor, every required floor run on main is red.

The three sentences are merged into the declaration's existing leading block.
That is a widening of scope, not a verbatim move -- an annotation's subject is
the declaration it attaches to -- and it is honest here because the function
exercises exactly the grammar and single-line cases the sentences describe.

Evidence, at this head: the module now typechecks and the claim evaluates to
`true`; the only refusal left is the `ProcessExit` wrapper that every `-> Bool`
claim fn gets under `gunbc run --function`. On origin/main the same invocation
refuses at the annotation.

This is the SECOND regression of a class already repaired twice (#11875, #11907).
Repairs treat instances; the supply is the required lane exiting 0 on a refused
phase, which is #11861.

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc-ci-auto-heal and others added 2 commits September 21, 2026 19:25
… phase word (review 69681)

The fix for the empty-blocker-set leak over-corrected. It deduped the synthesized
blockers BY PHASE WORD, and the phase words are not unique across the failure list:
`generated-artifact` carries six distinct sentences and `regen-fixed-point` four, so
every cause after the first in each phase was dropped from the receipt the separate
`--adjudicate-measurement-receipt` consumer reads. The exit stayed non-zero because the
set stayed non-empty, which is exactly why nothing caught it -- the status was right
while the located diagnostic was gone. That contradicted this diff's own stated
invariant and the ledger row's claim, both of which are corrected here rather than
restated.

THE RULE THAT SURVIVES BOTH DEFECTS. A failure sentence carrying its own cause is its
own located diagnostic and always reaches the set; only an EXACT duplicate cause is
suppressed. A failure spelled exactly as the bare phase word is a SUMMARY of blockers
that phase already minted in full, so it is suppressed only when that phase actually
has some -- and published when it does not, which is what keeps the original leak
closed. The old predicate held only the first property; its repair held only the second.

The fold moves into the library as `synthesize_phase_blockers` with five controls,
because it has now been wrong twice in two directions and neither was visible from the
process status. Mutation-verified: restoring the phase-word dedupe reds 2 of the 11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in e0b1a103575. The finding in review 69681 is correct, I verified it against the code rather than taking it on faith, and it is a defect this PR itself introduced.

Confirmed: generated-artifact carries six distinct failure sentences in this lane (claim_executor.rs:763, 779, 795, 842, 848, 862) and regen-fixed-point four. Deduping by phase word meant only the first cause per phase reached the published receipt. The exit stayed non-zero because the set stayed non-empty — which is precisely why it was invisible: the status was right while the located diagnostic was gone. That is this PR's own class one layer in, and the receipt is what the separate --adjudicate-measurement-receipt consumer prints.

The rule that survives both defects, rather than a revert to the old predicate (which held only the first property, as its repair held only the second):

  • a failure sentence carrying its own cause is its own located diagnostic and always publishes; only an exact duplicate cause is suppressed
  • a failure spelled exactly as the bare phase word is a summary of blockers the phase already minted, so it is suppressed only when that phase actually has some — and published when it does not, which keeps the original empty-blocker-set leak closed

Made executable. The fold moved to the library as synthesize_phase_blockers with five controls, including the discriminating red for this finding (three distinct generated-artifact causes must all reach the receipt) and the guard that a bare summary does not stack on real identified blockers. It has now been wrong twice in two directions and neither was visible from the process status, so it earns an executing test rather than a loop at a call site.

Mutation-verified: restoring the phase-word dedupe gives MUTANT_TEST_EXIT=101, 2 of 11 failing; the clean code passes 11/11. (Three earlier attempts at that check were themselves broken — output swallowed by tail, a waiter matching its own echoed command, and a cargo fmt reflow that made the patch silently no-op. Each would have handed me a confident "verified" backed by nothing, so the final run asserts the mutation applied before trusting the result.)

I also corrected the ledger row, which claimed "every one of them now reaches the blocker set." That was false as written; it now records the miss with its review id instead of restating the claim.

— sent from still-crane-198

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 21, 2026
@briansrls
briansrls added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 88632e1 Sep 22, 2026
4 checks passed
@briansrls
briansrls deleted the session/still-crane-198 branch September 22, 2026 03:59
@briansrls
briansrls restored the session/still-crane-198 branch September 22, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant