Skip to content

Install boundary: the planned/executed conjunct was x == x.clone(); give it a second producer - #10005

Merged
gunbai-bot[bot] merged 6 commits into
mainfrom
session/quick-bee-68
Sep 2, 2026
Merged

gunbai-bot[bot] merged 6 commits into
mainfrom
session/quick-bee-68

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The defect

v1.stage0.required_regen_host admit_stage_execution_from_model built one identity list
from the stage plan and passed it as both the planned and the executed field of
RegenStageExecutionObservation:

let identity_list = Value::List(Rc::new(identities.into()));
(ctx.sym("planned"),  identity_list.clone()),
(ctx.sym("executed"), identity_list),

So v2.workflow.regen_convergence_transaction regen_identity_population_eq evaluated
x == x.clone() on every install of the self-host convergence transaction. The conjunct ran, the
install boundary really blocked, and StagePlannedExecutedMismatch was authorable in
v2.test.claim.regen_convergence_transaction_witness_test while being unreachable from the
real acceptance path
— the split DESIGN §4b's authorable-RED rule exists to catch, and the
class the roster already carries as executed_conjunct_discriminates_nothing.

The repair: a second producer, not a second read

ObservedEffectPopulation is a digest delta over the seed source directory — pre-stage state
taken before the copy loop, post-stage state taken after the build. Its denominator is the
directory, not the plan. The checkpoint already journals this population, so this is a repoint
rather than a build: no new producer, no new walk.

What structurally prevents the two sides re-collapsing (not "a reviewer will notice"):
admit_stage_execution_from_model now takes planned: &[RegenConvergenceSurfaceReceipt] and
executed: &ObservedEffectPopulation. They are different types, and there is no conversion from
the former to the latter — from_stage_delta is the only constructor and it takes two directory
observations and never the plan. Re-collapsing them requires deleting a type.

The observation enumerates, it does not look up — and the boundary sentence in the first commit
was WRONG, not merely narrow.
It asserted coverage that did not exist: that a file the build
creates outside checkpoint_basenames was covered by the UnplannedPathMutated git
observation. An asserted coverage is worse than a declared gap, because a declared gap ranks for
fixing and an asserted one gets cited. The assertion was false: git_changed_stage0_paths runs
git diff --name-only, which reports tracked modifications and says nothing about untracked
files, so such a path was absent from the roster lookup, absent from git, and absent from the
restoration journal — three producers blind at once (review 58476). Both halves now walk the
directory with the roster as a floor, over every top-level regular file: a build has no business
writing into stage0_src, and a basename that resolves to no declaring module refuses as
SurfaceOwnershipUnresolved. Files that merely exist unchanged never reach that lookup, so the
hand-maintained population costs nothing and a build mutating one refuses whether or not git
tracks it.

Evidence

stage_execution_joins_the_plan_to_independently_observed_effects, three arms, because
one-directional identity checking is how this hid:

  • Arm A — an effect outside the plan. Stage 1 installs the producer, so its path is git-dirty
    when stage 2 begins and lands in changed_before; allowed_after is changed_before ∪ the
    planned paths, so UnplannedPathMutated cannot see stage 2's build rewriting it. Every
    already-dirty stage0 path is blanket-permitted for the rest of the transaction. This was
    green before.
  • Arm B — a planned surface the stage left byte-identical. This did refuse before, but as
    InstalledDigestMismatch — a content verdict standing in for a population one — so the arm
    asserts the cause, not that it refused.
  • Arm C — a file the build creates. fixture_created.rs, untracked, invisible to
    git diff --name-only. Reverting to the roster-only lookup makes this stage return Ok —
    green on a file invented by the build.
  • Positive control: stage 1 of the same test, where planned and observed effects agree and the
    stage is admitted. Without it a join that refused everything would satisfy every arm.
  • Mutation controls (two, one per producer defect): rebuilding executed from planned
    reddens exactly this test and nothing else (4 passed; 1 failed); so does reverting the
    directory enumeration to a roster lookup. That is the delete-arm half of the roster row's
    remedy, run twice.

Model side: regen_stage_execution_admission_label gains a StagePlannedExecutedMismatch arm
(the host error previously said only "Refused"), with two new .dag witnesses covering each
direction.

Ledger: a second specimen appended to gunbc.recurring_failure_mode
executed_conjunct_discriminates_nothing — the existing row covers this class, so this is a
receipt rather than a new row, and it carries the recognition rule this specimen adds: the delete
arm is not what found it; the producer count is.
docs/design-ledgers.md regenerated through
main_wet_one; the diff against main is that one row.

v1 seed freeze: required_regen_host.rs is hand-maintained seed, admitted under the PURPOSE
test (gunbc.v1_maintenance_standing) — this is a safety repair to the transaction that installs
the self-host generations, so it serves the v2 self-host program directly.

Rung honesty: this RED does not gate the merge

Determined positively, not inferred. .github/workflows/witnesses.yml: the required aggregate job
witnesses declares needs: [required-witnesses-build, required-witnesses-floor]. The step that
runs cargo test --release -p v1-compiler --lib lives in the rust-unit-tests job, which is
not among those needs — matching what DESIGN.md already says about that job ("runs on every
push and pull request but is not yet a needs of the required aggregate").

So the executed evidence exists and runs on every push, but no required run gates on it. Per
§4b(1)+(2) the honest rung for this class is mitigatable, not mechanically preventable: rung 2
depends on the mechanism executing and staying enrolled, and this one is not enrolled in a
blocking lane. Next-rung trigger — the capability, not an artifact: rust-unit-tests becoming a
needs of the required aggregate, which that job's own authority names as a one-row edit gated on
its measured wall clock. That is a CI-composition change with its own declared drop and does not
belong in a safety repair.

Checks

  • cargo test --release -p v1-compiler --lib regen_convergence — 5 passed, remotely, with
    CTRL_BUILD_FORWARD_ENV="GUNBC_MEMORY_BUDGET_BYTES" (dispatch printed
    forwarding env: GUNBC_MEMORY_BUDGET_BYTES). Correcting an earlier note in this PR: the
    BuildBuddy HostBudgetUnreadable refusal is an env-forwarding gap, not a platform limit —
    GUNBC_* is deliberately not forwarded by default, which is exactly the condition that makes
    the budget unreadable there. This family is remotely runnable. Also 5 passed locally.
  • cargo clippy --all-targets -- -D warnings — clean (remote).
  • cargo fmt --all --check — clean.

Note on the re-run of required-witnesses-build

That job failed at step 4, Install Rust toolchain, before any repo Rust is compiled:
error: $HOME differs from euid-obtained home directory: you may be using sudo — rustup refusing
because the runner's isolated $RUNNER_TEMP home does not match the euid's /home/ghrunner.
Nothing in this diff can reach that step.

Re-running is legitimate here on two conditions, and both are stated because a reviewer cannot
otherwise tell a diagnosed re-run from a green bought by retrying:

  1. The failure is diagnosed as host-specific and named, not merely "it went red". Across the
    last 25 witnesses.yml runs the same step failed on ten distinct branches from ~06:04Z, and
    joining runner_name to that step's conclusion gives srv1 12 fail / 5 pass, srv2-03 1 fail,
    srv3 3-for-3 success. A per-slot misconfiguration, which is also why a re-run lands green — it
    draws a different slot.
  2. The same content already has a clean full run behind it. Head 7e6e76f of this branch
    completed this workflow successfully at 05:26Z, before the onset.

witnesses.yml is deliberately not patched: it is generated from gunbc.witness_floor_workflow,
the isolation step is correct, and working around a broken slot from inside a safety repair would
bury a gating change in a diff nobody is reviewing for gating. The slot fault is an operator action
on the fleet and has been escalated as one.

Not in this PR

This is the first of two. The denominator loss proper — the admitted population never joined to
the terminal lineage, and a generic "excluded" conflating "we decided not to" with "we never saw
it" — lands next as a typed Applied | Deferred | Superseded | Refused disposition with
Deferred nonterminal. This one is separable and was the part that was lying rather than merely
incomplete.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G2171VCXrmcBnp6o4A8CzF

gunbc-ci-auto-heal and others added 4 commits September 2, 2026 05:20
…ive it a second producer

admit_stage_execution_from_model built ONE identity list from the stage plan and passed it as
both the planned and the executed field of RegenStageExecutionObservation, so the model conjunct
regen_identity_population_eq evaluated x == x.clone() on every install of the self-host
convergence transaction. The conjunct ran, the install boundary really blocked, and
StagePlannedExecutedMismatch stayed authorable in the .dag witness while being unreachable from
the real acceptance path -- DESIGN section 4b rung inflation, and the class DESIGN already
rosters as executed_conjunct_discriminates_nothing.

The repair is a second PRODUCER, never a second read of the first: ObservedEffectPopulation is a
digest delta over the journalled generated roster, taken before the copy loop and again after the
build. Its denominator is the roster, not the plan. It is a newtype with no conversion from the
plan, so the two sides cannot re-collapse without deleting a type.

Discriminating RED, both directions, because one-directional identity checking is how this hid:
an effect on a path OUTSIDE the plan (green before -- allowed_after blanket-permits every
already-dirty stage0 path for the rest of the transaction), and a planned surface the stage left
byte-identical (refused before, but as InstalledDigestMismatch -- a content verdict standing in
for a population one, so the arm asserts the cause). Stage 1 of the same test is the accepted
positive control.

Mutation control: rebuilding executed from planned reddens exactly the new test and nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2171VCXrmcBnp6o4A8CzF
…d to all three producers

review 58476 is right and the declared boundary in the first commit was FALSE. It said a build
that CREATES a generated file outside checkpoint_basenames is covered by UnplannedPathMutated.
git_changed_stage0_paths runs `git diff --name-only`, which reports tracked modifications and
says nothing about untracked files -- so such a path was absent from the roster lookup, absent
from git, and absent from the restoration journal. Three producers blind at once.

observe_generated_population_state now ENUMERATES the directory with the roster as a floor, over
every top-level regular file rather than the .rs generated ones: a build has no business writing
into stage0_src, and a basename resolving to no declaring module refuses as
SurfaceOwnershipUnresolved. Files that merely exist unchanged never reach that lookup, so the
hand-maintained population costs nothing and a build MUTATING one refuses whether or not git
tracks it.

Arm C of the RED is the reviewer's specimen: the build writes an untracked fixture_created.rs.
Mutation control -- reverting to the roster-only lookup makes that stage return Ok, i.e. GREEN on
a file invented by the build -- and exactly that one test reds.

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

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

review 58476, blocking finding — accepted and fixed in f8879e4.

You are right, and the declared boundary I wrote was false rather than merely narrow.
git_changed_stage0_paths runs git diff --name-only, which reports tracked modifications only,
so a file the build creates was absent from the roster lookup, absent from git, and absent from
the restoration journal. Three producers blind at once, which is worse than the one gap the
finding names.

observe_generated_population_state now enumerates the directory with the roster as a floor,
over every top-level regular file rather than only the .rs generated ones — a build has no
business writing anything into stage0_src, and a basename resolving to no declaring module
refuses as SurfaceOwnershipUnresolved. Files that merely exist unchanged never reach that
lookup, so the hand-maintained population costs nothing here and a build mutating one now
refuses whether or not git tracks it.

Arm C of the RED is your specimen: the build writes an untracked fixture_created.rs. The
mutation control is the part that makes it evidence — reverting to the roster-only lookup makes
that stage return Ok, i.e. green on a file invented by the build — and exactly that one test
reds.

Advisory on seed-Rust expansion — declining, with the reason. required_regen_host.rs is
hand-maintained v1 seed under an explicit standing: gunbc.v1_maintenance_standing freezes v1
semantics but keeps maintenance active under a PURPOSE test — a change is admitted when it serves
the v2 self-host program. This is the transaction that installs the self-host generations, so a
safety repair to its install boundary is squarely inside that test. Nor is there a scaffold
receipt to give: nothing added here is destined for deletion. ObservedEffectPopulation is the
terminal construction for this join, not a bridge to one — the model owns the verdict, the host
owns the observation, and that split is where it stays after the seed shrinks.

Two corrections to my own earlier claims, since both would be believed if left standing:

  1. I wrote that this test family "only runs locally" because BuildBuddy refuses
    HostBudgetUnreadable. That was a misdiagnosis: it is an env-forwarding gap. GUNBC_* is
    deliberately not forwarded to remote runners, which is exactly the condition that makes the
    budget unreadable there. With
    CTRL_BUILD_FORWARD_ENV="GUNBC_MEMORY_BUDGET_BYTES" GUNBC_MEMORY_BUDGET_BYTES=6000000000 the
    dispatch prints forwarding env: GUNBC_MEMORY_BUDGET_BYTES and all 5 pass remotely. The family
    is remotely runnable.
  2. This RED does not gate the merge, and the PR body now says so. witnesses.yml's required
    aggregate declares needs: [required-witnesses-build, required-witnesses-floor]; the step
    running cargo test --release -p v1-compiler --lib lives in rust-unit-tests, which is not
    among them. So the honest rung for this class is mitigatable, not mechanically preventable
    — §4b(2) makes rung 2 depend on the mechanism executing and staying enrolled. Next-rung
    trigger at capability grain: rust-unit-tests becoming a needs of the required aggregate.

— sent from quick-bee-68

gunbc-ci-auto-heal and others added 2 commits September 2, 2026 07:33
…lled arm to one word

The GHA runner reported "stage execution admission Refused" for a stage that must refuse as
StagePlannedExecutedMismatch. I cannot tell from that string which arm answered or which surfaces
disagreed, because the host binding rendered the model's label and discarded everything else --
diagnostic_name_mechanism_silent: the refusal is typed, its type is announced, and its located
detail never reaches the caller. §5 asks for a typed AND located diagnostic; only the first half
was being carried.

regen_stage_execution_admission_detail renders the populations for a mismatch, the digest pair for
InstalledDigestMismatch, and the reason for a build refusal; the host appends it to the error. A
detail that itself refuses is reported inline rather than dropped, since a silent renderer here
would rebuild the silence it repairs.

Two .dag witnesses: the mismatch renders both populations, and an admitted stage renders the empty
string so the renderer cannot be satisfied by returning text unconditionally.

This is not a fix for the CI red -- the cause is still unknown, and that is the point: the next
run says which arm answered instead of leaving me to guess.

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

gunbai-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

review 58543, the _ arm in regen_stage_execution_admission_detail — the finding is right, and
the fix is a type narrowing rather than a better fallback string.

You are correct that a fixed literal on _ is a soft absorbing shape: a future refusal variant
renders "refusal arm carries no renderer" with no location, and the only thing keeping that
honest today is the label the Rust caller prepends.

Writing a richer default would not repair it — it would make the widen more plausible, which is
the §5 trap the row names. The construction fix is that the type is wider than the producer's
range
: RegenStageExecutionRefused carries the whole RegenConvergenceRefusal coproduct (~25
variants) while regen_admit_stage_execution can only ever return three of them. Narrow that field
to a RegenStageExecutionRefusal of exactly those three and both the label and the detail
renderers become total with no _ at all — a new arm then fails to compile instead of
rendering a fixed string, which is the §4b(4) "unwritable rather than validated" form of the same
guarantee.

I am doing that in PR 2 rather than here, and the reason is not deferral. PR 2 adds refusal
arms to RegenConvergenceRefusal for the population/lineage joins — so it is the change that would
otherwise trip this exact hazard, and it is where the narrowing has to be correct anyway. Landing
the narrowing there means the new arms are covered by construction on the commit that introduces
them, instead of narrowing here and re-widening there. Doing it in this PR would also mean
re-touching every consumer of RegenStageExecutionAdmission in a diff whose subject is the
planned/executed producer.

Recorded so it cannot be lost: the _ arm in regen_stage_execution_admission_detail is a known
non-blocking gap, and PR 2 either deletes it by narrowing the type or explains in its own body why
it could not.

On your second note — agreed, ObservedEffectPopulation is deliberately a file-scoped host
implementation detail. The verdict is the model's; the newtype only carries an observation to it,
and it exists to be a type the plan cannot be converted into.

— sent from quick-bee-68

@gunbai-bot
gunbai-bot Bot merged commit 7f71ee3 into main Sep 2, 2026
5 of 6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/quick-bee-68 branch September 2, 2026 08:14
gunbai-bot Bot added a commit that referenced this pull request Sep 2, 2026
…refusal type so no `_` arm survives (#10035)

* Join the admitted population to the lineage, and narrow the refusal type so no `_` arm survives

THE DENOMINATOR LOSS. A stage receipt denominated in the population that REACHED it, so a surface
admitted at the boundary and dropped before planning appeared in no receipt at all -- not planned,
not deferred, not refused. Transaction success proved the narrowed set was handled and never that
the admitted set survived.

Three joins, every one an IDENTITY join with its residues named separately, because a count accepts
a member missing from one side standing against a phantom on the other:

  1. producer changed == planned UNION deferred, both directions, plus the disjointness a union
     cannot see -- a surface in both lists is one surface claiming to be handled and postponed at
     once, and a union absorbs it silently.
  2. planned == independently observed effects (landed in #10005).
  3. admitted-across-the-transaction == terminal lineage, both directions, AND every row terminal.

DEFERRED IS NONTERMINAL, which is the whole reason the disposition is a coproduct. A generic
`Excluded` merges "we decided not to" with "we never saw it" -- both render as absence. Applied,
Superseded and Refused end a lineage; Deferred does not, so a transaction whose lineage ends in a
promise refuses instead of reporting a fixed point it never reached.

THE LINEAGE IS BUILT FROM THE STAGE RECEIPTS, NEVER FROM THE ADMITTED LIST. Deriving it from the
admitted population would make `admitted_without_lineage` empty by construction -- the same
x == x.clone() shape #10005 removed, one level up.

THE REFUSAL TYPE NOW EQUALS THE PRODUCER'S RANGE. `RegenStageExecutionRefused` carried the whole
~25-variant `RegenConvergenceRefusal` while `regen_admit_stage_execution` can return three, so both
renderers needed a `_` arm -- a new variant would render a fixed string with no location and nothing
would refuse. Narrowing to `RegenStageExecutionRefusal` DELETES the arm rather than improving its
default: a fourth refusal fails to compile in both renderers. It already worked once here, catching
the fixture producer during this change.

Evidence: 9 .dag witnesses and one host test asserting each refusal's own name and residues, with
positive controls -- an exact partition admits, and Applied+Superseded closes a lineage that was
never installed, so a join that refused everything cannot pass.

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

* Narrow the population refusal too: the wildcard came back in the type this change added

review 58572: RegenPopulationAdmission carried the whole ~25-variant RegenConvergenceRefusal
while its two producers return three, so both new renderers needed a `_` arm -- rebuilding, in
the change that removes the hazard from stage execution, the same silent-degradation surface.

RegenPopulationRefusal now holds exactly those three and both renderers are exhaustive. The
variants live there and nowhere else: no consumer outside the joins produces one, so a copy in
RegenConvergenceRefusal would be a second authority for one meaning.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
Integrating #10005, #10014, #10017, #10024 and #10025 so this branch is judged
against the base in use rather than 4605989. No overlap with the files this
branch touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EN2pmV7GbBZYhCZWYqFbCc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants