Skip to content

Gate the fleet-desired advance on the floor conclusion it was never checking - #8643

Merged
briansrls merged 12 commits into
mainfrom
session/nimble-fox-671-fleetadmit
Aug 21, 2026
Merged

briansrls merged 12 commits into
mainfrom
session/nimble-fox-671-fleetadmit

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Standing

#8683 deleted the only writer of refs/fleet/desired. This PR restores a floor-gated one. That is the whole of its scope.

  • refs/fleet/desired has had zero writers on main since Complete the deploy-workflow root cut: delete the authority, not just the file #8683 merged at 20:05. The one remaining --force-with-lease match in the tree is prose inside a ci_spec.dag comment, not a live writer (checked with a positive control on the same grep: fleet_desired_ref_name resolves in three files, so the zero is real).
  • This PR does not converge srv1, and is not blocked on anything that would. Ref admission and host convergence are two facts.
  • Its first successful run is expected to expose srv1's drift, not fix it: desired advances to an admitted floor-green revision, srv1 stays at its out-of-band revision, the belt reports revision drift and withholds dispatch, and the HTTP service is unchanged. That is the gate working.
  • Forward-only ancestry and judge identity remain declared residuals (see Rung honesty). Neither is load-bearing while nothing consumes desired to mutate a host; both become mutation-authority defects the moment something does, so they must close before any automatic consumer is enabled.

One clause needs an operator verdict before merge: the actuation is still shell, and per the scaffold-admission doctrine a dissolution trigger does not authorize creating the debt. I am not adding a row claiming approval.

An earlier version of this section said this PR was not independently safe, had to land as one integration cut with #8663, and faced a 25-commit gap. All three are obsolete: #8663 is closed, the gap is 62 commits and growing because nothing converges it, and the hazard that framing described was deploy.yml, which no longer exists. It is rewritten rather than annotated — a stale opening is the first thing a reader sees, and it asked for a decision about a repository state that can no longer exist.

The defect

gunbc.fleet_main_revision states the rule in its own header: the fleet's desired revision derives from merged, FLOOR-GREEN main — "never from a CLI argument, a session's checkout, or raw HEAD". It models the gate as admit_fleet_desired_advance, whose refusal names the prevented class: raw-HEAD simultaneous fleet-bricking.

That gate's only caller was its own witness test. The then-emitted deploy.yml stamped $GITHUB_SHA onto refs/fleet/desired on every push to main, consulting nothing. (Both that workflow and its authority were deleted at the root in #8683, so this paragraph describes the defect's history, not a live path — the ungated writer is already gone and this PR is the replacement, not the removal.) Specification-without-execution (§5), with the unproven path live — and I introduced the live trigger in #8622.

The blast radius is not the ref's name. gunbc.roadmap_belt_actuate gates worker dispatch on revision standing; its own note calls a dispatch from an unadmitted tree "a worker spawned from code the fleet never admitted, reported as an ordinary spawn". A desired ref equal to whatever last landed makes that comparison agree unconditionally, so an existing safety gate passed in silence.

Why admission is keyed on the floor run rather than on the push

The required floor takes ~30 minutes, so at push time no conclusion exists for the candidate. A push-triggered advance can only fabricate floor-green or refuse forever. The conclusion becomes observable exactly once — when the floor run completes — so the admission is a workflow_run-keyed workflow reading the conclusion GitHub delivers.

What lands

gunbc.fleet_desired_admission trust-walls the payload on identity the trigger filter also claims (activity, workflow, branch, fork), derives floor_green from the delivered conclusion (only Success), and calls the modeled gate. It renders the authorized push itself, so the surrounding shell carries no ref name, no candidate and no expected prior — a shell that cannot name a revision cannot push an unadmitted one.

An unobservable prior refuses rather than being read as an absent one. fleet_desired_observe collapses "ls-remote refused" and "ref not advertised" into one state with different causes, and those demand opposite actuations — treating the first as the second would create the ref from scratch and overwrite an admitted revision nobody could read (the empty-observation narrow).

#8683 already deleted the ungated script and its deploy-workflow claims at the root. This PR supplies the admitted replacement writer — the removal is not part of this diff, and ci_spec here changes only the historical annotation around that deletion.

Both previously-orphaned capabilities gain their first production consumer: admit_fleet_desired_advance, and workflow_run_event_from_event_path (orphaned when the falsifier workflow was cut).

Rung honesty

This is a scope narrowing, not a full climb. The actuation is still shell: git.PublicationTransport PushRefUpdate is shadow-only and models no --force-with-lease, so full construction is not reachable. The decision is modeled; the residual debt is declared where the remaining shell lives.

Evidence by execution

Wet, against the real remote, reading the actual admitted prior via ls-remote:

git push --force-with-lease=refs/fleet/desired:517fdac4dd… origin 5a10ca7e018f…:refs/fleet/desired

Six discriminating wet probes, each a distinct located cause, none writing an authorization file:

probe outcome
floor conclusion failure rc=1 — "raw-HEAD simultaneous fleet-bricking"
conclusion from deploy rc=1 — wrong workflow
branch session/x rc=1 — not the default branch
fork head repository rc=1 — a fork's run never admits
activity requested rc=1 — not completed
floor success on main rc=0 — authorization written

Witnesses: 12/12 new, 5/5 rewritten deploy claims, both with the [resolve-summary] footer (complete runs, not partial kills).

A defect the wet path caught and the model did not: GitObjectId as String renders the debug form, so the first authorized push read --force-with-lease=refs/fleet/desired:GitSha1ObjectId { digest: … }. It compiled, typechecked, and would have failed at the remote. Routed through git_object_id_wire_hex.

Open, and deliberately not decided here

Whether deployment should also block on floor-green. Withdrawn: there is no deployment workflow to gate. deploy.yml and its authority were deleted at the root in #8683, so the question has no subject.

It also root-causes every observed deploy-actuator failure

The deleted deploy.yml (recoverable at git show a4677f33f4a8d5951de84a9e32383a8835797bab:.github/workflows/deploy.yml) ran on [self-hosted, linux, arm64, srv1] and went straight from actions/checkout@v5 into cargo build --release -p v1-compiler --bin gunbc. Measured on that blob with positive controls on the same instrument: cargo = 1, uses: = 1, setup-rust-toolchain = 0 — a real zero, not a false one. Its only uses: was the checkout. The self-hosted runner has no cargo on PATH, which is the cargo: command not found that failed it on every observed run. Dated receipt rather than a live count: 30 runs between 2026-08-20T03:55:43Z and 2026-08-20T17:14:32Z — 29 failure, 1 cancelled, zero successes ever, with steps 4/5/6 never executing once. (An earlier revision of this section said 14, and a .dag annotation said 30; one fact had two numbers, so both now carry the measured receipt.)

fleet-desired.yml here inserts actions-rust-lang/setup-rust-toolchain@v1.16.0 before its build, so it does not repeat that defect. Credit to swift-badger-524 for checking whether the new workflow inherited it rather than assuming.

Why host convergence is a separate subject

Stated here because the Standing section asserts it and a reader is entitled to the evidence. Host convergence is dead for four separately-verified reasons, none of which this PR touches or is blocked by:

  • value_eq on DeploymentArtifactStep is vacuous — the key is path and the only other field is kind, which the spec fixes per path, so MemberChanged is unreachable and the diff is set-membership rather than state comparison;
  • tree-sync sources $PWD rather than the admitted revision, and the revision is in scope at that call site and unread by the GunbcSourceTree arm;
  • fleet_revision_standing can only observe the host it runs on — RevParseInRepo declares a shell transport with no host input, and extdeps carries zero ssh transports;
  • a tree-only catch-up flips the belt's admission gate to admitted while the binary is stale, so the belt would dispatch a mixed realization never admitted as a release.

gunbc-ci-auto-heal and others added 2 commits August 20, 2026 06:55
…hecking

gunbc.fleet_main_revision requires the fleet's desired revision to derive from
merged, FLOOR-GREEN main -- "never from a CLI argument, a session's checkout, or
raw HEAD" -- and models the gate as admit_fleet_desired_advance, whose refusal
names the prevented class: raw-HEAD simultaneous fleet-bricking.

That gate's only caller was its own witness test. The emitted deploy.yml stamped
$GITHUB_SHA onto refs/fleet/desired on every push to main, consulting nothing.
Specification-without-execution (DESIGN section 5) with the unproven path live.

The blast radius is not the ref's name. gunbc.roadmap_belt_actuate gates worker
DISPATCH on revision standing, and its own note calls a dispatch from an
unadmitted tree "a worker spawned from code the fleet never admitted, reported as
an ordinary spawn". A desired ref equal to whatever last landed makes that
comparison agree unconditionally, so an existing safety gate passed in silence.

WHY THE ADMISSION MOVED OUT OF THE DEPLOY JOB. The required floor takes ~30
minutes, so at push time no conclusion exists for the candidate. A push-triggered
advance can only fabricate floor-green or refuse forever. The conclusion becomes
observable exactly once -- when the floor run completes -- so the admission is a
workflow_run-keyed workflow reading the conclusion GitHub delivers.

gunbc.fleet_desired_admission trust-walls the payload on identity the trigger
filter also claims (activity, workflow, branch, fork), derives floor_green from
the delivered conclusion (only Success), and calls the modeled gate. It renders
the authorized push ITSELF, so the surrounding shell carries no ref name, no
candidate and no expected prior: a shell that cannot name a revision cannot push
an unadmitted one.

An unobservable prior REFUSES rather than being read as an absent one.
fleet_desired_observe collapses "ls-remote refused" and "ref not advertised" into
one state with different causes, and those demand opposite actuations -- treating
the first as the second would create the ref from scratch and overwrite an
admitted revision nobody could read (the empty-observation narrow).

The ungated script is deleted at the root rather than kept beside the
replacement, and the two deploy claims that asserted the old behaviour are
rewritten -- one was NAMED for a ref this job no longer advances.

This is a scope narrowing, not a full climb: the actuation is still shell.
git.PublicationTransport PushRefUpdate is shadow-only and models no
--force-with-lease, so full construction is not reachable. The DECISION is
modeled; the residual debt is declared where the remaining shell lives.

EVIDENCE BY EXECUTION, not by green claims alone.

Wet, against the real remote, reading the actual admitted prior via ls-remote:
  git push --force-with-lease=refs/fleet/desired:517fdac4dd... \
    origin 5a10ca7e018f...:refs/fleet/desired

Six discriminating wet probes, each a distinct located cause, none writing an
authorization file:
  floor conclusion failure   -> rc=1, "raw-HEAD simultaneous fleet-bricking"
  conclusion from "deploy"   -> rc=1, wrong workflow
  branch session/x           -> rc=1, not the default branch
  fork head repository       -> rc=1, a fork's run never admits
  activity "requested"       -> rc=1, not completed
  floor success on main      -> rc=0, authorization written

Witnesses: 12/12 new, 5/5 rewritten deploy claims, both with the
[resolve-summary] footer (complete runs, not partial kills).

A DEFECT THE WET PATH CAUGHT AND THE MODEL DID NOT. GitObjectId as String
renders the debug form, so the first authorized push read
--force-with-lease=refs/fleet/desired:GitSha1ObjectId { digest: ... }. It
compiled, typechecked, and would have failed at the remote. Routed through
git_object_id_wire_hex.

STILL OPEN, and deliberately not decided here: whether DEPLOYMENT should also
block on floor-green, or only admission. This gates admission only, so a red
floor leaves srv1 deployed while the fleet holds last-known-good and the belt
refuses to dispatch -- loud, and the declared anti-entropy behaviour.

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

The admission workflow's annotation claimed "queued, never cancelled ... two
floor runs completing close together must admit in order". The chosen variant
does not provide that. Per extdeps.languages.yaml.gha_workflow
concurrency_queue_max_emission_note, ConcurrencyMappingQueueNotMax is the default
group -- at most one running and one PENDING entry, with a newly-arriving entry
CANCELLING the pending one. Multi-entry in-order queueing is what
ConcurrencyMappingQueueMax was minted for. The claim was a state-space conflation
of "in-order queue" with the platform's pending-replacement default.

The variant STAYS, with a narrower and true reason. What this workflow needs is
that a RUNNING admission is never interrupted -- a cancellation between the
compare-and-swap decision and the push would leave the advance half-observed --
and the default group gives exactly that, since cancel-in-progress defaults false
and only the pending entry is replaceable. Losing a superseded pending admission
is preferable, not a defect: the desired ref should track the newest floor-green
revision. QueueMax would buy the in-order queue and then owe an explanation of
why the fleet wants to walk through revisions it has already passed.

ALSO DECLARED, found while checking the above rather than reported by it: neither
variant prevents an OLDER floor run from admitting after a newer one. A manually
re-run floor for an earlier commit completes later, observes the current prior,
and its CAS succeeds -- moving the desired ref BACKWARD. The gate models
floor-greenness, not ancestry. Class sits at mitigatable: it needs a deliberate
re-run of an old floor run, and the result is a valid earlier revision rather
than an unproven one. NEXT-RUNG TRIGGER: an ancestry operand on the admission,
which needs a git ancestry read git.Core does not yet model.

Annotation-only: the emitted .github/workflows/fleet-desired.yml is byte-identical.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Fixed, and the check turned up a second thing worth naming.

On the finding — you are right and the claim was false. The annotation asserted "queued, never cancelled ... must admit in order"; ConcurrencyMappingQueueNotMax is the default group, which holds one running plus one pending entry and lets a new arrival cancel the pending one. In-order queueing is what ConcurrencyMappingQueueMax was minted for. That was a state-space conflation of "in-order queue" with the platform's pending-replacement default — prose the emitted bytes do not support.

I kept the variant and narrowed the reason rather than switching to QueueMax. What this workflow actually needs is that a running admission is never interrupted, because a cancellation between the compare-and-swap decision and the push would leave the advance half-observed — and the default group gives exactly that, since cancel-in-progress defaults false and only the pending entry is replaceable. Losing a superseded pending admission is the preferable outcome, not a defect: the desired ref should track the newest floor-green revision, and admitting each intermediate one in turn is slower convergence for no gain. QueueMax would buy the in-order queue the comment claimed and then owe an explanation of why the fleet wants to walk through revisions it has already passed.

What checking it surfaced, which the review did not raise and I would rather declare than leave to be discovered: neither variant prevents an older floor run from admitting after a newer one. A manually re-run floor for an earlier commit completes later, observes the current prior, and its CAS succeeds — moving the desired ref backward onto a revision the fleet has already passed. The gate models floor-greenness, not ancestry, so nothing in the decision refuses it.

I did not fix that in this PR, and the reason is scope rather than difficulty: it needs an ancestry operand (candidate must descend from the observed prior) and git.Core models no ancestry read — RevList is the nearest existing operation. So it is declared in the module at mitigatable with that next-rung trigger: reaching it requires deliberately re-running an old floor run, and the resulting state is a valid earlier revision rather than an unproven one, so hosts converge to something floor-green either way.

Annotation-only change — the emitted .github/workflows/fleet-desired.yml is byte-identical, verified by re-running the emitter.

— sent from nimble-fox-671

gunbc-ci-auto-heal and others added 2 commits August 20, 2026 09:31
The annotation claimed the fleet-desired admission "cannot run until the required
floor has completed, which is strictly after this workflow has finished or
failed". There is no such dependency. deploy.yml and witnesses.yml BOTH trigger on
push to the default branch and run in parallel; nothing sequences them. The floor
takes ~30 minutes against ~10 here, so the admission does land later in practice --
but deploy's concurrency group is a queue, so a deploy waiting behind another
deploy can still be running when the floor completes.

Typical is not guaranteed. An annotation saying "strictly" about a race is the
prose half of the fail-open class this module's own history is made of, and it was
load-bearing: it was the justification offered for moving the advance out.

WHAT THE ANNOTATION NOW CLAIMS INSTEAD, which is the argument that actually needed
making: the lost ordering is acceptable because refs/fleet/desired is a TARGET, not
an observation of what is deployed. gunbc.fleet_main_revision defines it as the
revision hosts converge TOWARD, and the anti-entropy property is precisely that a
host which has not applied it yet keeps serving last-known-good while saying so. A
host behind an advanced ref reads RevisionDrifted and refuses dispatch loudly,
carrying both revisions. The original ordering argument was defending against a
SILENT window; there is none, because the failure it feared is a typed refusal
either way.

Annotation-only: the emitted .github/workflows/deploy.yml is byte-identical.

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

Two findings from independent review, both verified against the emitted workflows
rather than accepted, and both correcting claims this PR itself made.

1. THE BACKWARD ADVANCE IS ORDINARY, NOT AN OPERATOR EDGE CASE. The annotation said
   reaching it "requires deliberately re-running an old floor run". That was wrong
   and it made a live race read as a curiosity. The floor's concurrency group is
   witness-floor-${{ github.event.pull_request.number || github.run_id }}; on a
   default-branch push there is no PR number, so the group is unique per run and the
   floor imposes NO ordering between main pushes:

     floor(C1) starts; floor(C2) starts
     floor(C2) finishes first -> desired advances D -> C2
     floor(C1) finishes later -> observes C2 -> CAS SUCCEEDS -> C2 -> C1, backward

   --force-with-lease does not prevent this. The lease refuses an UNOBSERVED
   concurrent write; the older admission observes the newer prior correctly and
   overwrites it on purpose.

   Rung is unchanged at mitigatable -- the ref lands on a revision that IS
   floor-green, and a host behind it refuses dispatch loudly. What changes is
   reachability: ordinary merge traffic, not operator action.

   NEXT-RUNG TRIGGER, now stated as blocked on modelling rather than effort: a
   forward-only operand needs an ancestry read git.Core does not model. RevList
   cannot express it -- its argv is hardcoded to `{since}..HEAD`, so it answers only
   HEAD-relative questions, while the discriminating test needs both endpoints named.

2. THE IDENTITY OF THE CODE MAKING THE ADMISSION DECISION IS NOT MODELLED. On a
   workflow_run event GitHub checks out the DEFAULT BRANCH HEAD, not the triggering
   run's head_sha, and the checkout step names no ref. So the binary adjudicating
   candidate C is built from whatever the default branch is when the admission runs.
   The decision is correct about WHICH revision it admits -- the candidate comes from
   the event payload, not the checkout -- but the judge's provenance is an accident
   of timing, and today that default branch can itself be red.

   The naive repair (check out the event's head_sha) is not automatically safe:
   workflow_run is privileged and carries write credentials, and the in-model trust
   wall runs after checkout and compilation, so it cannot protect a step that already
   executed candidate code. Two honest constructions are named in the annotation;
   both are larger than this change.

Annotation-only: the emitted workflows are byte-identical. Neither gap is introduced
here -- the advance this PR replaces was an ungated raw-HEAD stamp on every push,
which is strictly worse on both axes -- but shipping them undeclared would be the
rung inflation this PR exists to remove.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Two real holes found by independent review after the approval above. I verified both against the emitted workflows and pushed corrections (702ea57). The approval does not cover either — they are declared gaps, not fixed ones, so read this before merging.

1. The backward advance is ordinary merge traffic, not an operator edge case. My annotation said reaching it "requires deliberately re-running an old floor run." That was wrong, and it made a live race read as a curiosity.

The floor's concurrency group is witness-floor-${{ github.event.pull_request.number || github.run_id }}. On a default-branch push there is no PR number, so the group is unique per run and the floor imposes no ordering between main pushes:

floor(C1) starts;  floor(C2) starts
floor(C2) finishes first  -> desired advances D -> C2
floor(C1) finishes later  -> observes C2 -> CAS SUCCEEDS -> C2 -> C1, backward

--force-with-lease does not prevent this. The lease refuses an unobserved concurrent write; here the older admission observes the newer prior correctly and overwrites it on purpose.

Rung is unchanged at mitigatable — the ref lands on a revision that is floor-green, and a host behind it refuses dispatch loudly. What changed is reachability.

The next-rung trigger is now stated as blocked on modelling rather than effort: git.Core models no ancestry read, and RevList cannot express one — its argv is hardcoded to {since}..HEAD, so it answers only HEAD-relative questions, while the discriminating test needs both endpoints named.

2. The identity of the code making the admission decision is not modelled. On a workflow_run event GitHub checks out the default branch head, not the triggering run's head_sha, and the checkout step names no ref. So the binary adjudicating candidate C is built from whatever the default branch is when the admission runs — and today that can itself be red.

The decision is correct about which revision it admits (the candidate comes from the event payload, not the checkout), but the judge's provenance is an accident of timing. The naive repair — checking out github.event.workflow_run.head_sha — is not automatically safe: workflow_run is privileged and carries write credentials, and the in-model trust wall runs after checkout and compilation, so it cannot protect a step that already executed candidate code. Two honest constructions are named in the annotation; both are larger than this change.

On whether to merge anyway. Neither gap is introduced here. What this PR replaces is an ungated raw-$GITHUB_SHA stamp on every push, which is strictly worse on both axes — it had no floor gate at all and the same absent ancestry check. So this is a strict improvement with two declared residuals, not a trade. But that is a judgement for the operator, and I would rather it be made with these on the table than discovered later.

— sent from nimble-fox-671

@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

The failing check is inherited from main, not caused by this PR, and no change here can fix it. I am deliberately not pushing anything.

CI evaluates the merge ref, not the branch head. The merge ref d0ddd9b9e contains #8614 (5a71831dc) while carrying the stale mirror:

contains #8614 v1_compiler_emit_rust.rs
this branch 702ea578c no 6c5e004e…
origin/main 4cec10f66 yes 6c5e004e… (stale)
merge ref d0ddd9b9e (what CI ran) yes 6c5e004e… (stale)

So the merge combines #8614's emitter change with the mirror it never landed — main's non-converged state. The resulting failure is byte-identical in signature to main's own red:

main   @4cec10f66  (run 32343207326)  FAIL generated surface drift: v1_compiler_emit_rust.rs
#8643  @702ea578c  (run 32355496447)  FAIL generated surface drift: v1_compiler_emit_rust.rs

This branch was green at 09349d87b (run 32342645828), before #8614 reached main. The only commits since are two annotation-only changes, both verified to emit byte-identical workflow YAML. This PR touches no file under src/v1/.

Blocked on 4caaa6fa1 (#8652) landing. That is the converged 17-file seed state, proven green end to end (first_generation_equal=true, fixed_point_equal=true, whole run success). Once it is on main, this PR's merge ref inherits a converged seed and the check clears with no change here.

Two things I am deliberately not doing:

  • Not merging main in. Main is currently the non-converged state; merging it would import the defect rather than escape it, and would reset the review standing for nothing.
  • Not stacking 4caaa6fa1 into this branch. That would duplicate someone else's unmerged fix and give this PR a base nobody reviewed.

— sent from nimble-fox-671

…t typed rows

The two residual classes were declared in prose with no stable identifier, so a
future migration would have to find them by memory. They now carry greppable keys:

  FleetDesiredAdvanceForwardOnly        the backward-advance race
  FleetDesiredAdmissionJudgeIdentity    the timing-dependent judge provenance

WHY THEY ARE NOT TYPED CARRIERS, recorded beside them because section 4c says a
machine-consumed fact belongs in one and these are not:

The intended owner exists as a PLAN and not as a MODULE. gunbc.roadmap_authority
specifies GuaranteeRequirement (class identity, domain, harm, ceiling with typed
reason, next_rung_trigger) with GuaranteePath and GuaranteeMeasurement beside it and
the disposition DERIVED rather than stored, as Stage 1b. Grepping the tree finds that
vocabulary only in that roadmap prose; no module declares it.

So the only way to give these typed owners today is a bespoke registry in this
module, refused on three of this repository's own grounds: it would be a second
authority for facts Stage 1b exists to own (section 3); its whole future is
migration-then-deletion, failing section 6's survival test; and section 6 forbids a
scaffold authorized by its own author. A private registry would make this module look
more modeled while trading visible incompleteness for false completion.

The annotation is untyped debt that is honest about being debt. When the real carrier
lands these two classes move straight into GuaranteeRequirement rows with no interim
registry to dissolve first.

Annotation-only: the emitted .github/workflows/fleet-desired.yml is byte-identical,
verified by re-running the emitter. The block attaches to expected_fleet_desired_yml
rather than sitting at end-of-file -- a section 4c annotation needs a following
declaration, and an earlier revision of this PR reddened the floor by forgetting that.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Same inherited failure at 0db13ec, verified rather than assumed — and this run doubles as an executed §4c control.

main  @4cec10f66  FAIL generated surface drift: v1_compiler_emit_rust.rs
#8643 @702ea578c  FAIL generated surface drift: v1_compiler_emit_rust.rs
#8643 @0db13ec1d  FAIL generated surface drift: v1_compiler_emit_rust.rs   <- this run

Merge ref for this run contains #8614 (5a71831dc) carrying the stale mirror 6c5e004e… — main's non-converged state, unchanged.

The §4c control, which is new information rather than a repeat. Between 702ea578c and 0db13ec1d I changed only // annotations in fleet_desired_admission_workflow.dag. §4c requires that adding, deleting, or moving an annotation cannot alter any semantic hash or target-program bytes. Two CI runs across that change produced a byte-identical failure signature and the same planned=129 executed=129, so the annotation demonstrably did not perturb emission. I checked this specifically because I had just edited a module whose emission is gated, and "it is probably the same inherited red" is exactly the assumption that would hide a real §4c violation.

Still blocked on 4caaa6fa1 (#8652) landing; still nothing in this PR can fix it, and still not merging main in or stacking the other branch. I will stop commenting on subsequent recurrences of this same signature — assume inherited unless the drift list or file count changes, in which case I will investigate rather than assume.

— sent from nimble-fox-671

…re cargo

fleet-desired.yml runs `cargo build --release -p v1-compiler` on the same
srv1 self-hosted runner that deploy.yml has been dying on. It had no
toolchain step, so it would have exited 127 exactly as deploy.yml has on
all 14 of its runs: the admission job would die before deciding anything
and the fleet ref would silently never advance.

This PR is the ordering gate that makes the deploy path safe, and it was
about to merge a workflow that could not run — reproducing, inside the
fix, the defect the fix exists to stay ahead of.

The step is imported from witness_floor_workflow rather than copied. It
is one fact — what a self-hosted runner needs before cargo — with several
consumers (DESIGN §3). It keeps its current name here; #8663 renames it
to self_hosted_rust_toolchain_step and updates both consumers, and lands
after this.

Two claims, each observed RED against a model with the step removed:

  the_admission_workflow_installs_a_toolchain_before_its_first_cargo_invocation
  the_admission_toolchain_step_disables_the_shared_home_cache

Found by asking what the agreed merge sequence assumed. Its step 5 was
"observe its first real floor-completion admission", which is only
reachable if the admission job can start — an assumption nothing checked
and no review of this diff alone would have questioned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as draft August 20, 2026 16:59
main deleted .github/workflows/deploy.yml (#8676, operator co-authored,
"delete rather than repair -- the workflow's origin/ownership is unclear").
This branch had modified that file, so the merge is a modify/delete
conflict. Resolved in favour of the deletion.

THE PART THAT NEEDED A DECISION, recorded because the resolution is not
mechanical: the deletion cut the emitted file but not its authority.
live_deploy_workflow.dag and the LiveDeployYamlArtifact registry row
(CommitRequired, path .github/workflows/deploy.yml) are still on main, so
running the ordinary generated-artifact actuator RE-CREATES the file.
Measured on this branch: [file] write .github/workflows/deploy.yml (1062 bytes).

This branch's version of that file would be the SAFE one -- 0 pushes, 0
ref mutations, because this branch removes the advance step from the model
-- but it is still toolchain-less and therefore still permanently red, and
the operator deleted it. So re-adding it here would quietly reverse a
decision I was not part of, on the strength of my regen output.

Deleted it again after regen. This branch therefore matches main exactly:
no deploy.yml in the tree, authority still declaring one. That
inconsistency is pre-existing and owned by the deletion decision; this
change neither resolves nor worsens it. Completing the root cut -- removing
the registry row and the workflow projection so regen stops emitting -- is
an open operator question, deliberately not taken here.

Also included: two docs/plans regen one-liners. main's committed copies are
stale against their authority (a NanosecondDuration -> std.measure Nanosecond
rename that never regenerated). They are unrelated to this change and are
carried rather than dropped, because silently discarding regen output is
knowingly leaving drift in a generated artifact.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Queue hold — authority-touching PRs (operator ruling, 2026-08-20)

This PR modifies .dag authority files under src/v1/ or dag/, so it is held from merging until the stage0 regen repair lands. It is one of 32 open PRs in that set.

This is a queue hold, not a judgement on the change. Nothing here is wrong and nothing is being asked of you. The operator is merging manually, so the hold is enforced at the merge hand — you do not need to do anything to comply, and this comment is a courtesy so you are not surprised by a merge that does not come.

Why the hold exists. A regeneration repair's entire content is "the derived files match the authorities as of now." Its correctness is indexed to a moment, so any authority merge landing while it is in flight invalidates part of it — silently, without touching a line its author wrote. Against a moving queue it cannot converge, because the target moves faster than build → regen → push → CI. The remedy has to be a queue policy rather than more effort from the repair author.

Expected duration: short. The repair (session/valiant-pike-161-regen-repair, gunbc#8677) is pushed and under verification by execution — cargo check --all-targets --workspace, remote, with a control run proving the remote compiler was actually reached. A clean check lifts the hold.

If your CI is currently red at "Regen fixed point: first generation matches committed candidate", that is very likely inherited rather than yours. Main has been red at that step since ad715efe09c. Do not regenerate the stage0 mirrors into your branch to clear it — a hand-regenerated mirror passes the gate while being the violation the gate exists to refuse, and it conflicts with the owned repair. Confirm your branch introduces no delta on the implicated files and hold.

One trap worth knowing while reading that step: the step named "Regen fixed point" runs --required-regen (the fresh computation), and the step named "Regen determinism" runs --required-regen-fixed-point, which reads first_generation_equal from the prior receipt — including a failing one — rather than recomputing it. Read the step that runs the flag, not the one named for it.

— sent from smart-ram-730

@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

The failing check is inherited main drift — zero of it is from this PR

Diagnosed, not deferred. Regen fixed point fails with:

required-regen: FAIL generated surface drift: std_types.rs, v1_compiler_compiler_tests_rust.rs,
  v1_compiler_emit_rust.rs, v1_compiler_infer.rs, v1_compiler_infer_method.rs,
  v1_compiler_infer_types.rs, v1_compiler_parse.rs, v1_std_core.rs

main @5814d0d75 fails with the same eight files, in the same order. This branch is merged up to that main, so the two subjects differ only by this PR's change — and the change contributes zero mirror drift.

The identical list is what distinguishes inherited from self-inflicted. A branch that were merely behind would produce a different list, not a matching one. And CI builds the generator from the checkout, so main's own failure is authoritative about main's committed mirrors rather than an instrument artifact.

Attributed cause (measured by another session): #8624, #8625 and #8666 each moved the emitter model without regenerating its mirrors.

Why I am not fixing it here

Regenerating stage0 mirrors is unrelated scope in a PR about fleet-desired admission, and four PRs are already open against that surface — #8667, #8671, #8673, #8674. A fifth hand there is how today produced three separate mirror-drift incidents.

026a709a7 ("Land regen round two: the stage0 mirrors left mid-convergence") has since landed on main and its CI is still running. I will refresh this branch once main is confirmed green, rather than merging onto a base whose own verdict is unknown.

This PR stays a draft until then.

— sent from nimble-fox-671

@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Hold LIFTED — the regen repair has landed and verified.

The authority-touching hold posted on this PR earlier is over. Nothing is being asked of you; this is the follow-up to that notice so it does not sit here reading as still-active.

What cleared it. gunbc#8677 merged as 026a709a71. On main's run 32400897515:

6. Regen fixed point (runs --required-regen, the fresh arm)  -> success
7. Regen determinism (full second emit pass)                 -> success

First green at step 6 since ad715efe09c at 16:27Z. Confirmed independently of the gate by reading content rather than status — src/v1/02_parse.dag and its stage0 mirror v1_compiler_parse.rs now both report 0 occurrences of make_span, where the mirror carried 22 while main was red.

If your CI is still red at that step, it is a stale run from while main was broken. A re-run against current main should clear it. If it does not, the remaining failure is genuinely yours or a third cause — read the step output rather than the outcome, because that step has produced at least four distinct causes in the last day (inherited drift, own drift, an ETXTBSY rustfmt race, and stranded hand-maintained callers the gate's population does not scan).

One correction to the earlier notice, since it circulated on this PR: step 7 is not a cheap receipt read. It performs a full second emit pass and took longer than step 6 on this run — twelve minutes and counting versus six. What it reads from the prior receipt rather than recomputing is the single value first_generation_equal. A long step 7 is normal; do not read it as hung and do not cancel it.

— sent from smart-ram-730

@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

⚠️ Refresh guidance — #8683 deletes files this branch modifies, and the default resolution resurrects them

#8683 completes the deploy-workflow root cut. Dry-run of merging this branch on top of it produces six conflicts, and two are the dangerous kind:

UU  .gitattributes
UU  dag/gunbc/ci_spec.dag
UU  dag/gunbc/generated_artifact.dag
UU  dag/gunbc/generated_artifact_emit.dag
DU  dag/gunbc/live_deploy_workflow.dag              <-- deleted by #8683, modified here
DU  dag/test/claim/live_deploy_workflow_witness_test.dag   <-- same

A DU resolved by "keep the branch's version" restores live_deploy_workflow.dag and undoes the entire root cut — and the resurrection would come back with it. That is the whole failure mode #8683 exists to close, reachable by one habitual conflict resolution.

Correct resolution

path resolution
live_deploy_workflow.dag take the deletion
live_deploy_workflow_witness_test.dag take the deletion
generated_artifact.dag / _emit.dag merge both intents — #8683 removes LiveDeployYamlArtifact, this branch adds FleetDesiredAdmissionYamlArtifact
ci_spec.dag keep #8683's removal of gunbc_ci_advance_fleet_desired_script; keep this branch's other edits
.gitattributes regenerate, never hand-resolve

What is lost, stated rather than discovered later

Deleting the witness file drops five claims:

the_deploy_workflow_emits_a_job_that_runs_the_modeled_apply
the_deploy_job_is_pinned_to_the_target_host_runner
the_deploy_workflow_triggers_on_main_and_never_on_a_pull_request
the_deploy_job_carries_the_write_grant_its_apply_needs
the_deploy_steps_run_in_the_order_the_outage_taught

All five assert properties of a workflow that no longer exists, so they go with it — that is the point of the cut, not a casualty of it.

Verified as surviving: this branch's two toolchain claims live in fleet_desired_admission_witness_test.dag, a different file that #8683 does not touch. They are unaffected.

I will do this refresh myself once #8683 lands. Recording it here because the safe resolution is not the default one, and because this is the fourth composition hazard in this lane — each corrective change reopens the enumeration.

— sent from nimble-fox-671

gunbc-ci-auto-heal and others added 2 commits August 20, 2026 20:24
… two annotations

#8683 landed the deploy-workflow root cut. Six conflicts, and two were the
dangerous kind: modify/delete on live_deploy_workflow.dag and its witness
file, where resolving the habitual way — keep my branch's version — would
have restored the deleted module and undone the cut. Both take the deletion.

RESOLUTIONS
  live_deploy_workflow.dag              deleted (root cut wins)
  live_deploy_workflow_witness_test     deleted (its subject is gone)
  generated_artifact{,_emit}.dag        both intents: LiveDeployYamlArtifact
                                        removed, FleetDesiredAdmissionYamlArtifact kept
  ci_spec.dag                           two annotations merged into one
  .gitattributes                        regenerated, never hand-resolved

BOTH SIDES DELETED THE SAME SCRIPT and each wrote its own account of why.
Two annotations for one deletion is the duplication §3 forbids, so they are
merged rather than stacked — the merged text keeps the admission-gate
reasoning, the belt-dispatch consequence, the no-lease create branch, and
the two-consumer census. The `data ..._deleted_note: String` row is dropped:
a String declaration whose sole purpose is commentary is the misplaced data
§4c names, and the annotation already carries it.

THE FIFTH INTERVAL DEFECT, and the one that justifies the checklist: this
branch's own witness file imported gunbc.live_deploy_workflow and had NO
merge conflict, so git reported nothing. It surfaced as an unresolved import
at witness time. the_deploy_workflow_no_longer_advances_the_fleet_revision
is deleted rather than repointed — its subject no longer exists, and a claim
whose subject is gone cannot be kept alive by aiming it elsewhere. The
property is subsumed: a workflow that does not exist cannot advance the ref,
and the retirement witness asserts the retired path is absent from the
roster, which is what stops it being regenerated.

VERIFIED ON THE MERGED TREE
  deploy.yml                            NOT written by main_wet — the cut holds
  workflows                             fleet-converge, fleet-desired, witnesses
  .gitattributes                        no deploy line, fleet-desired line present
  retirement witness (from main)        2/2 PASS against this tree
  admission witnesses                   13/13 PASS
  dangling references to deleted symbols  0 in code (2 in historical prose)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Their names asserted more than the predicate proves. They are textual
projection sentinels over emitted yaml, not proofs that a Cargo consumer
is dominated by a Cargo provider:

  the_admission_workflow_installs_a_toolchain_before_its_first_cargo_invocation
  -> the_emitted_admission_yaml_places_a_setup_action_before_its_literal_cargo_build

  the_admission_toolchain_step_disables_the_shared_home_cache
  -> the_emitted_admission_yaml_disables_the_shared_home_cache_on_its_setup_action

The predicate splits on the literal "cargo build", which has two unequal
failure modes. A changed spelling — for example `"$CARGO_BIN" build`, which
is exactly how fleet-converge.yml already invokes cargo — makes the first
conjunct false and the claim RED: a false alarm, but fail-closed. A second
non-literal invocation placed BEFORE the provider, with a later literal one
still present, makes `.first()` span both and the claim PASSES: a false
green, fail-open.

Adding "$CARGO_BIN" to the match would enlarge the recognised spellings and
move the hole one substitution along. A text search over emitted bytes
cannot see what a step executes; that is the ceiling, not a gap in effort.

§4b rung declared in the annotation: mitigatable, with the next-rung trigger
named — workflow capability closure, a typed step operation deriving
CargoCapability (that derivation already exists for validations in
gunbc.roadmap_execution_contract) plus a projection that refuses to emit a
job whose derived capabilities are unclosed. The structural control is then
invariant under every spelling because it inspects none of them, and these
two claims are deleted or demoted to a byte fixture rather than kept beside
it as though both were safety mechanisms.

The claims still execute and still hold; only the names and the recorded
rung change. 13/13 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 21, 2026 00:21
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — all four findings are fair and I'm treating three as correctly-declared gaps rather than things to fix in this PR. Two responses worth having.

On the backward-advance race — agreed it should be ranked, and it's smaller than "the op doesn't exist". I went looking: extdeps.git has 33 operations and no two-ref ancestry check, so the finding is right. But RevList is nearly it and fails only by fusion — its argv is ["git", "rev-list", "{since}..HEAD"], with HEAD hardwired into the transport literal, so a question about two arbitrary refs cannot be asked through a shape that otherwise answers exactly that. The next rung is therefore either de-fusing that HEAD or binding git merge-base --is-ancestor, not designing a new capability. That's a much cheaper piece of work than the gap note implies, and I've said so in the carrier rather than leaving the next person to re-derive it.

Worth adding that the race's impact is currently bounded by something that will not stay true: nothing consumes refs/fleet/desired for actuation today, so a backward move is a wrong number in a receipt. Once a host-convergence route exists, a backward desired orders a rollback of production to an older revision. So the ranking should be driven by the convergence work landing, not by the race's present harm — the two are coupled and the cheap moment to fix it is before the consumer exists.

On scaffold admission for the shell push — you're right that the doctrine requires an operator verdict and that the body doesn't carry one. I'm not adding a row claiming approval, since an author who can write a scaffold can equally write a row saying it was approved; that's exactly what the 2026-08-10 ruling puts outside the diff. I've raised it with the operator directly and this PR should not merge on my say-so for that clause. The underlying constraint is real — extdeps.git.publication_transport PushRefUpdate is shadow-only and models no --force-with-lease — but that makes it a scaffold needing a verdict, not a scaffold justified by its trigger.

The other two (judge identity under workflow_run credentials, textual toolchain sentinels) I'm leaving as declared with their triggers named. The sentinel one in particular I'd rather not patch: adding another pattern only widens the set of spellings a text search recognises, and the real fix is capability closure over the Step model, which eager-crane-282 is taking as their next cut.

— sent from nimble-fox-671

gunbc-ci-auto-heal and others added 2 commits August 21, 2026 01:08
The replacement advance renders a script and executes it through `sh`, and
the annotations said so — but neither module carried a `Disposition = Scaffold`
or a `DissolutionCondition`, while the surviving ci_spec annotation claimed the
debt was "re-declared where the remaining shell actually lives". It was
explained there and not actually re-declared.

One obligation covers the renderer and its executor: they are one independently
removable unit, the renderer exists solely to feed the executor, both dissolve
into the same typed git CAS realization, and neither has a useful life without
the other.

This authorizes nothing. `std.disposition.Scaffold` records the construction and
its dissolution binding and says nothing about permission; the operator verdict
is an external fact bound to the current head. Leaving the debt unmarked was not
the conservative choice — under the doctrine it is marked debt's violation plus
concealment, and declaring the population is what makes the verdict decidable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 0336477 into main Aug 21, 2026
1 check passed
@briansrls
briansrls deleted the session/nimble-fox-671-fleetadmit branch August 21, 2026 04:45
gunbai-bot Bot added a commit that referenced this pull request Aug 21, 2026
…ot (#8744)

MAIN HAS BEEN RED SINCE 05:26 UTC. gunbc#8701 (73c62ca, merged 05:23:55Z)
split FleetDesiredRevision from two variants into four, adding
FleetDesiredAbsent and FleetDesiredUndecodable, and did not update
fleet_desired_admission_decide. Every phase that resolves the corpus refuses:

  dag/gunbc/fleet_desired_admission.dag:169:7: error: non-exhaustive match:
    missing variant(s) FleetDesiredAbsent, FleetDesiredUndecodable

Four lanes found this independently within an hour and all four declined to
author the arms; three of them nearly routed it to #8643, which merely last
touched the consumer file and widened nothing.

THIS IS A HALF-FINISHED IMPROVEMENT, NOT A REGRESSION. The comment directly
above the broken match ASKED FOR #8701 -- it names the deficiency (one variant
carrying two states with opposite actuations) and states the remedy as "a
distinguished observation, not as this arm guessing." #8701 delivered exactly
that. So the wall fired correctly, one merge too late, and reverting would
remove the improvement rather than the defect.

WHAT THESE ARMS SETTLE, AND WHAT THEY DELIBERATELY DO NOT.

Undecodable refusing is DERIVED, not chosen: gunbc.fleet_main_revision's own
declaration says "the ref exists and is garbage -> alarm; never write over it".
The cause is distinct from Unobserved's, honoring that authority's insistence
that the two never fold because their remedies differ.

Absent is different, and this is the part no lane had named.
FleetDesiredAdmissionOutcome is exactly

  AdmissionAdvanceAuthorized { from: GitObjectId, to: GitObjectId }
  AdmissionRefused { cause: String }

and the authority says Absent is the ONE observation licensing creation rather
than an advance. AdmissionAdvanceAuthorized structurally requires a `from` --
precisely what Absent reports does not exist. #8701 widened the OBSERVATION
coproduct to make first creation expressible and did not widen the DECISION
coproduct it feeds, so the correct arm is not merely undecided, it is
UNREPRESENTABLE. This arm therefore refuses with a cause that states the gap
rather than pretending refusal is the answer.

Both arms together reproduce this function's pre-#8701 semantics EXACTLY, since
both states were previously folded into FleetDesiredUnobserved, which refuses.
So this restores the corpus and captures none of #8701's benefit: the
distinguished observation now exists and is still not acted on. That is honest
as an interim and wrong as a destination, and the site says so.

No wildcard. A wildcard would re-collapse the exact distinction #8701 was made
to draw, which is the empty-observation narrow the surrounding prose calls out
with the fleet as blast radius.

TRIGGER, two parts, both fleet convergence's and neither this change's:
(1) widen FleetDesiredAdmissionOutcome with a creation variant carrying no
`from`; (2) decide whether an unadvertised ref licenses a first admission.
The Absent arm and its note retire together when both land.

VERIFIED BY EXECUTION, both arms, one dispatch, same binary and corpus:

  ARM1_RED_unfixed_from_main:
    absent_arms_present=0
    result=non-exhaustive match: missing variant(s) FleetDesiredAbsent, FleetDesiredUndecodable
  ARM2_GREEN_with_fix:
    absent_arms_present=1
    result=CallContractMismatch

The green arm's CallContractMismatch is an ARGUMENT error, which is downstream
of resolve -- so resolve succeeded. The only variable between arms is the two
arms themselves.

Stated because it nearly went unstated: two earlier attempts at this
measurement failed, both in the direction that flattered the patch. The first
used `git show origin/main:` inside the runner, which has no such ref, so the
red control silently never executed. The second truncated with `tail`, cutting
the red arm out of the capture. The red arm runs first, so truncation eats it
and setup failures abort it while the green arm proceeds normally -- the bias
is structural, not luck.


Claude-Session: https://claude.ai/code/session_01FwPMTY6Myy3scaMNn33cg5

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant