Skip to content

Merge main into #12040: reconcile attempt gate with #12021 ceiling retry - #12123

Closed
briansrls wants to merge 51 commits into
mainfrom
session/bold-lynx-559-pkg4-lifecycle
Closed

briansrls wants to merge 51 commits into
mainfrom
session/bold-lynx-559-pkg4-lifecycle

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session jolly-ibex-659.
Pushing to session/bold-lynx-559-pkg4-lifecycle advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

gunbc-ci-auto-heal and others added 30 commits September 21, 2026 14:04
…nt of leases

The local compute provider admitted work by listing its lease directory and
comparing the count against a policy budget of two. That is not atomic -- the
list and the create are two operations on different names, and O_EXCL excludes a
NAME, never a QUANTITY, so two requests with different identities both read one
lease in flight and both proceeded -- and a count is not a capacity: two
producers on a 125 GiB host and two on a 502 GiB host are the same policy
applied to machines that differ fourfold.

Admission is now a seat in a memory pool. The homes are the ones DESIGN 3b names
for fabric/compute: product.capacity.pool owns the appropriation, encumbrance,
term and release law; product.capacity.pool_events owns the transitions;
gunbc.fabric_event_log linearizes each append with one compare-and-set against
the partition head. gunbc.compute.host_capacity declares which pool, where it is
linearized (this host, not the fleet DB -- a host's own memory is not a fact the
fleet contends on, and routing it through another machine would refuse local
builds on a network fault), and joins it to a real reading of /proc/meminfo.

The unit's MemoryMax is the amount the pool granted, so the ledger's arithmetic
is the enforcement rather than a parallel description of it. Every ending --
succeeded, failed, mismatch, infrastructure refusal -- releases, and a release
that did not land is reported rather than assumed.

Disposition: compute_cold_build_cap is DELETED, and HostAtColdBuildCap with it.
Its fact is now HostComputeCapacityFull, measured in bytes; the two arms beside
it are facts a count could not express (the machine's live availability, and an
unreadable ledger). The appropriation is two worker ceilings, exactly the memory
the deleted count admitted.

Also: fabric_seat_acquire/observe generalize over the measure (memory is not a
dimensionless count), and the observe/append/retry loop gunbc.fabric_quota held
alone moves to the carrier as fabric_pool_event_append, with quota settlement
migrated onto it rather than a second copy growing beside it.

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

review 69561 on #11962. The presence pass walked meminfo_field_names and
refused the first missing name; the record was then built from a SECOND
spelling of those names, each falling back to kibibyte(count: 0) on an absence
the presence pass had supposedly ruled out. Two lists for one concept is
DESIGN 3's nicknaming, and the fork was load-bearing: the zero's
unreachability was an invariant held AT A DISTANCE between roster and record,
so renaming or dropping a roster entry made mem_total and mem_available read
0 KiB -- which compute_reserve_on turns into a permanent below-floor or
appropriation-exceeds-total refusal ATTRIBUTED TO THE MACHINE rather than to
the parse. A fabricated plausible output behind a guard that looked like a
wall (DESIGN 5).

The guard is deleted rather than strengthened. Each field is matched once, its
absence names itself, and its value is the one that match just bound:
meminfo_required and its zero are gone, so the fabricated reading has no
constructor left (DESIGN 4b). meminfo_field_names no longer claims to be this
fold's authority -- what the parse demands is exactly what ProcMeminfo has
fields for, a fact the record carries and cannot drift from itself.

The witness gains the control the absence arms cannot supply: every field
carries a DISTINCT value and every field is asserted, so a name paired with
the wrong field is red; and a field missing from the MIDDLE of the file names
itself.

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

The workload ran under the grant on srv1: a real cargo release build of
v1-compiler as gunbc-compute-1bf2556cc4b015f9.service, capped at the 26 GiB the
pool granted, binaries returned by content. It also surfaced three defects that
no wet cell could see, because none of them touched the provider.

1. THE CONSUMPTION READING BROKE THE ATTACH PATH. It was appended to
   outcome.json, which the attach path PARSES (stored_outcome_decode), so every
   later caller of a SUCCEEDED identity read StoredOutcomeUnreadable and
   rebuilt -- the contract's "two callers, one build" silently dead. Measured:
   the provider's own dependent compile request refused on a dependency that
   had in fact succeeded. The reading moves to its own file beside the log; a
   realization's measurement does not change the shape of the contract's
   document.

2. THE RESERVATION REFERENCE WAS THE WORK IDENTITY. The identity is stable by
   construction -- that is its job -- so a second request for the same identity
   hit the encumbrance ledger's DuplicateReference. The pool's reference names
   one HOLD, so it is now per attempt; the identity stays in the receipt.

3. A LEDGER REFUSAL WAS REPORTED AS A FULL POOL. The seat carrier renders every
   pool refusal into SeatFull, so DuplicateReference reached an operator as
   "capacity full" on a host with 400 GiB free. product.capacity.pool_events
   names the marker it writes (pool_full_wire) so both sides read one authority,
   and anything else is carried through as a ledger refusal. The typed-arm fix
   widens SeatAcquisition and every match on SeatFull, so it is a declared
   frontier with its trigger rather than a silent widen here.

New wet control, red under the conflation: a reused reference on a pool with
room for both requests is a ledger refusal and never ComputeCapacityFull.

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

The fourth thing the srv1 run found, and the one that makes the case for
running it. DESIGN 4c admits standalone leading blocks attached to MODULE-SCOPE
declarations only. The witness route this provider is covered by tolerates a
block inside a function body; `gunbc compile` refuses it outright. So
work_provider_local carried 14 blocking errors while every witness over it
reported green, and the real compile request on srv1 is what said so -- the
provider had stopped compiling and nothing in the wet or hermetic evidence
could see it.

The prose is not deleted: it moves above compute_provide_leaf, which is the
declaration it was describing.

Verified: `gunbc compile --entry dag/gunbc/compute/work_provider_local.dag`
reports 0 blocking errors at this head, against 14 before.

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

that disagreed with its caller, and the recovery route that did not exist

Side-chat review of #11962 at dd240f0, plus review 69595. All four are real
and all four were verified against the code before anything was changed.

A. THE DOUBLE PROMISE. Pool capacity and live MemAvailable were checked
   separately, so with a 52 GiB appropriation, 30 GiB available and two 26 GiB
   requests, each request saw 26 <= 30 and each fit the appropriation, and BOTH
   admitted -- neither could see the other's granted-but-not-yet-consumed
   memory. The compare-and-set was doing its job perfectly on a question that
   was missing a term.

   The fix is not a third check. The pool already computes the committed total
   inside the linearized fold, so the machine's reading belongs in the SAME
   comparison: the ceiling an attempt is adjudicated against is the smaller of
   the declared appropriation and what the host says is free. This is
   worst-case-sound and conservative, and says so: MemAvailable already excludes
   what outstanding grants have touched, so a resident grant is counted twice.
   The exact relation needs the resident share, which is observable per unit and
   is a declared frontier with its trigger. product.capacity.pool already ruled
   that replay never adjudicates the ceiling, so a moving ceiling cannot poison
   the fold -- it makes the pool over-committed, which refuses new acquires
   until it drains.

B. THE POOL WAS KEYED BY THE INSTANCE, NOT THE HOST. srv1 declares two
   instances -- srv1-live and srv1-lab -- with different ids and roots, and the
   memory they spend is ONE machine's. Both halves are now host-keyed: the
   partition names the host identity, and the ledger is rooted under the
   instance that host resolves to through dashboard_instance_for_host, the same
   host-to-instance authority the fabric event log already uses. A host that
   authority cannot resolve reserves nothing rather than defaulting to the
   caller.

C. THE RECORD DISAGREED WITH THE CALLER. A run whose work succeeded but whose
   release failed wrote WorkSucceeded to outcome.json and handed its caller a
   refusal -- and the next caller ATTACHED to that success, so the leaked seat
   had no consumer that would ever look for it. The ending and the unresolved
   obligation are two facts and are recorded as two: the obligation is its own
   durable file, and the outcome document carries the refusal the caller was
   given, so a later caller attaches to exactly what this caller was told.

D. THE RECOVERY ROUTE DID NOT EXIST (review 69595). The module's prose said a
   wedged seat is recovered by an explicit release naming why; compute_release
   took a ComputeReservation whose only constructor lives and dies inside one
   compute_provide_leaf, so under QuiescenceRequired -- which never lapses --
   nothing in the corpus could discharge it, and two such events would consume
   the whole appropriation and turn every later request into a capacity refusal
   on a host with its memory free. A claimed route no declaration reaches is
   the dangling consumption DESIGN 3c refuses.

   compute_release_reference takes the reference, which is all a recovering
   operator has, and compute_release_cli is its executing consumer. Both doors
   go through fabric_seat_release, the checked mirror of fabric_seat_acquire:
   read, fold, PROPOSE against the folded pool, append only what it admits. That
   check is load-bearing rather than defensive -- an unheld reference appended
   anyway would not fail at the append, it would fail at every later READ, when
   the fold hits UnknownEncumbrance and refuses the whole partition.

Controls, all executing wet, seven cells: two requests summing past the observed
headroom cannot both admit (RED when the backing ceiling is reverted to the
appropriation); two instances on one host resolve to one pool at one root; a
held seat is recoverable by reference and an unheld one writes nothing, asserted
beside a later successful reservation so the ledger is shown still readable.

gunbc compile on the provider closure: 0 blocking errors.

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

supply their own inputs

Side-chat review of #11962 at 725d9c5. All three blockers verified against the
code first; all three were real.

1. QUIESCENCE WAS DECLARED AND NEVER OBSERVED. compute_release ran
   unconditionally on what compute_run_leased returned, and that is the exit of
   `systemd-run --wait` -- a LAUNCHER, not the unit's parent. A launcher killed,
   timed out, or disconnected from the bus yields ComputeExecFailed WHILE THE
   UNIT KEEPS RUNNING, so the seat's memory would be handed to the next arrival
   on top of a live cargo. product.capacity.lease already models this exactly
   (QuiescenceFact) and this provider was not consulting it.

   Settlement and recovery now both consume a termination decision bound to the
   actual unit, read through the existing systemd.Systemctl.ShowUserProperty.
   Only two arms free capacity: the manager has no record of the unit (never
   started, or ran and was collected -- the same fact for this decision), or it
   reports it stopped. Running keeps the charge, and so does UNOBSERVED, which
   is the arm a fail-open would swallow and the one a lost launcher produces. An
   ActiveState this fold has never seen is unobserved, not permission.

   The recovery CLI observes it too: an operator releasing "because the worker
   looked dead" was asserting the one fact nothing checked. A running unit is
   refused by name with the state that refused it.

2. OBLIGATIONS WERE KEYED BY IDENTITY WHILE RESERVATIONS ARE KEYED BY ATTEMPT,
   so a retry overwrote the first obligation and the first seat became
   unrecoverable -- the reference that was the only way to release it was gone.
   One obligation file per attempt, named by the reference, created exclusively
   so a second writer cannot replace one. A retry now CONSUMES that lifecycle
   state: an identity with an outstanding obligation refuses by name and points
   at the release verb rather than reserving on top of a charged seat and
   publishing over the record carrying it. And the producer lease is dropped
   LAST -- it was deleted before the outcome was written, so the exclusion held
   for the name and not for the publication it exists to protect.

3. THE HEADROOM FIXTURE DERIVED ITSELF FROM THE THING UNDER TEST. appropriation
   = 4 x (available/2 + 1MiB) exceeds MemTotal on a lightly loaded host, so the
   cell refused before reaching the comparison it existed to make -- and on a
   busy host it passed for unrelated reasons. It was flaky in both directions.
   compute_reserve_against takes the observation as a value and compute_reserve
   supplies the real one, so the admission cells state the relation exactly:
   each request fits available, two exceed it, two fit the appropriation, and
   the appropriation fits MemTotal. Not a skip -- the pairing obligation is
   discharged by the_real_observation_reads_this_machine, which asserts the real
   producer reads THIS machine, and by the srv1 receipts.

Also, per the accepted (A): the relation is described as admission against an
observed MemAvailable ESTIMATE, and explicitly not a no-OOM guarantee. The
unit's MemoryMax bounds one workload; this stops the POOL from over-promising,
and conflating the two would be the rung inflation DESIGN 4b forbids.

Eight wet cells and the termination fold all pass; gunbc compile on the
provider closure: 0 blocking errors.

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

and the memory carriers the refusals were stripping

Split approved by the manager. All seven of the side chat's blockers at 9172cf7
are lifecycle/recovery; none touches admission or the pool. The argument for
splitting is safety, not workload: WITHOUT a recovery route every one of those
defects degrades to "the seat stays charged" -- the host under-admits and
nothing is over-promised. WITH a half-built one they FREE SEATS THAT SHOULD NOT
BE FREED. Shipping recovery half-built is worse than not shipping it.

OUT OF THIS PR, and named rather than quietly dropped: compute_release_cli,
compute_release_reference, the obligation files and the retry-blocking read.

BLOCKER (1) IS NOT RECOVERY-ONLY AND IS FIXED HERE, per the manager's
correction: automatic settlement releases too. ActiveState alone is not the
evidence -- `failed` can coexist with live processes while a unit's stop reaches
its SIGKILL timeout, and an empty ActiveState is the manager declining to answer,
not authoritative absence. The decision is now four-valued over evidence the repo
already models: authoritative absence from LoadState=not-found (the wire value
this corpus reads positively), observed termination from the unit's own cgroup
reporting populated 0, populated, and unavailable. Only the first two free
capacity; unavailable keeps the charge, and that is the arm that matters, because
a lost launcher, an unreadable manager and a missing cgroup all look like it.

BLOCKER (7) STILL REACHED PR 1 AND IS FIXED: the lease-undropped arm handed the
caller a refusal while the outcome document already said the run SUCCEEDED, then
claimed the next request would refuse as ProducerInFlight -- three claims that
cannot all be true, since the stored record is what the next caller attaches to.
One contract now: the stored record is the result, the caller is told what the
next caller will read, and the stale lease is recorded as an operational fact
rather than converted into a verdict about the work.

Blockers (2)-(6) are no longer reached: each was about the recovery door or the
obligation files, and both are out.

THE GAP IS CLASSIFIED AS THE MANAGER ASKED. No discharge route existed on main
-- admission was a count of lease files and there were no reservations at all --
so this is not a rung that fell and is not a 4b(3) drop. It is a new class:
gunbc.recurring_failure_mode a_held_compute_seat_has_no_in_corpus_discharge,
rostered, with a trigger naming PR 2's CAPABILITY (termination from absence or
an empty cgroup; attempt-to-unit binding; coordination with a pending producer;
a settled state a retry consumes) rather than an artifact. Its remedy sentence
reaches the caller through compute_stuck_reservation_remedy.

review 69624, unit modeling: HostBelowMemoryFloor carried Int fields NAMED _kib
while its producer already had Kibibyte and the provider called kibibyte_count
to strip the carrier on the way in -- the unit modeled twice and checked nowhere.
It carries Kibibyte now. And SeatRoomObserved.headroom was the boundary THIS PR
moved: a bare Nat was right while the pool was fixed at seats, and generalizing
fabric_seat_observe to Pool<Q, S> made the same field mean KiB for one caller and
seats for another. SeatStandingObservation is now generic and headroom carries
Measure<Q, S, Nat>.

Seven wet cells plus the termination fold pass; gunbc compile on the provider
closure: 0 blocking errors.

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

texts that told a reader the wrong thing

Side-chat review at 5ee0cce plus review 69645. All four verified against the
code first.

THE THIRD RELEASE ARM. observe_unit_cgroup read an empty ControlGroup as
UnitTerminatedObserved without reading any cgroup at all, and an empty or
unrecognized successful LoadState could reach it. An empty ControlGroup does not
mean finished: systemd realizes the cgroup AT SPAWN, so it equally describes a
start that is still PENDING -- which makes a whole unbacked launch reachable.
Reserve; the start is pending; the LAUNCHER is lost while this driver lives on;
settlement sees the empty property, releases; the job then starts against memory
nobody holds.

The arm is deleted and an empty required property is never promoted to a
conclusion -- it is UNAVAILABLE like every other absence of evidence. Capacity is
now freed on two facts that establish the attempt ENDED, plus one that requires a
realized cgroup:

  THE ATTEMPT COMPLETED -- `systemd-run --wait` returned success, which it does
  only after the unit ran to completion. A lost launcher does not return success
  and a pending job has not returned at all. This is a SEPARATE fact from the
  work's ending, and conflating them is what let a lost launcher free a live
  unit's memory: a WorkFailed says the unit exited nonzero OR that the launcher
  never got an answer.

  THE MANAGER HAS NO RECORD -- LoadState=not-found.

  A REALIZED CGROUP REPORTING populated 0 -- which cannot be a pending start,
  because the cgroup exists only from spawn onward.

THE DECISION IS NOW A FOLD OVER SUPPLIED READINGS (UnitObservations), so the
controls sit at the OBSERVATION-TO-DECISION boundary rather than at the
variant-to-release mapping, which cannot see any of the defects that actually
occurred: a failed query whose text reads like an answer, an empty property, a
pending start. A failed query is unavailable whatever it printed -- the exit
status decides first and the text is never consulted.

review 69645, THE DISCARDED HOST RECORD: `if measured.success { settled } else
{ settled }` had identical arms, so a failed write vanished by construction. The
compound case was the sharp one -- the producer lease failing to drop AND that
write failing left an identity whose lease is permanently held with NO RECORD
ANYWHERE that it is stale, while the caller got an ordinary success. It now
refuses, naming what was lost. This does not reopen blocker (7): that was a
refusal DENYING a published success; this one affirms the run's ending, says the
stored record is authoritative, and reports a host fault that happened after
publication.

review 69645, THE DEAD CITATION: fabric_seat_release's annotation named
compute_release_reference and compute_release_cli, which the split removed. It
now names its real consumer and states that the operator door is the capability
PR 2 lands.

TWO REMEDY TEXTS THAT MISLED. compute_stale_lease_note said the next request
would refuse as ProducerInFlight, contradicting the contract the same block
establishes: a caller reaching a published SUCCESS attaches, and the attach is
decided before the lease is consulted -- the stale name only blocks a request
that must PRODUCE again. And compute_stuck_reservation_remedy told an operator to
clear the partition by hand, which under live admission removes the record the
admission arithmetic folds and trades a host that under-admits for one that
over-promises; it now refuses that and states the ordering any manual
intervention requires.

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

the charge; the record and the caller stop disagreeing

Side-chat review at 773825c, plus review 69663. All four verified first.

(1) THE ORDER OF THE TESTS WAS THE DEFECT. attempt_completed was checked FIRST
    and returned immediately, so {completed, populated 1} RELEASED -- a
    launcher's success overriding the kernel saying processes are still there.
    `--wait` returns when the MAIN process exits, and under KillMode=process or
    none a forked child outlives it in the unit's cgroup.

    Both halves of the reviewer's either/or are taken, because each is
    independently right. KillMode=control-group is now BOUND on the invocation
    rather than inherited -- a default is not a declaration, and a drop-in could
    change it while this provider went on treating a completed wait as an empty
    cgroup. AND contrary evidence wins regardless: a positive populated=1
    refuses the release before anything else is considered and is never
    discarded.

(2) A MOMENTARILY EMPTY CGROUP IS NOT TERMINAL. populated=0 with completion NOT
    established used to release, and a realized cgroup can be empty between
    execs. In this PR an uncertain completion keeps the charge; the population
    reading now only CONFIRMS a completion or REFUSES one. UnitTerminatedObserved
    is gone as a releasing arm. Establishing termination without a completed
    attempt is the lifecycle capability's job.

(3) THE RECORD AND THE CALLER DISAGREED AGAIN, one layer along: the lost-record
    repair published the real outcome and synthesized a
    WorkRefusedByInfrastructure for the caller, so the caller and the next
    attacher read different contracts -- the same fusion as before, in the
    opposite direction. The two facts are now returned as two. ProvideResult
    carries the stored outcome UNCHANGED plus a HostRecordStatus beside it, so
    re-requesting an identity yields exactly the ending the first caller was
    told. The CLI turns a lost record into a nonzero exit naming both halves,
    which is how the recording failure stays visible without becoming a verdict
    about work that really ran.

review 69663: seat_release_landed had no consumer anywhere in the tree --
compute_release_on matches SeatRelease structurally and the provider uses its own
compute_release_landed. Deleted rather than given a consumer, because the carrier
already answers the question by match (DESIGN 3c).

Controls, all at the observation-to-decision boundary: a completed wait with
populated=1 keeps the charge; populated=0 without completion keeps the charge; a
pending start establishes nothing; a failed query whose text reads like an answer
is unavailable; and a lost host record changes no contract the next caller reads.

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

A completed wait may confirm a release only because KillMode=control-group means
systemd tears the cgroup down with the unit. Spelled inline at the call, that was
an assertion nothing checked; the properties are a fold now, and
the_unit_binds_the_termination_policy_the_release_decision_relies_on is what
makes "bound" a fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/ci/ci_layer_roots.dag
…terminal

consumer; the KillMode control claims only what it checks

Side-chat verdict at f5344b2. All three verified first.

(1) THE PREDICATE READ "not occupied AND (absent OR completed)", so completed +
    UNKNOWN released -- and the witness ASSERTED that, which is the worse half:
    a control that pins the defect is not a control. It is now exactly: occupied
    keeps; authoritative absence releases; completed AND POSITIVELY EMPTY
    releases; everything else keeps. A bound KillMode says what systemd WILL do
    and is not a reading of what is true, so an unreadable cgroup establishes
    nothing beside a completed wait. The witness is flipped.

    The KillMode control now claims only what it checks: it reads the argv this
    provider CONSTRUCTS -- invocation wiring -- and does NOT establish what
    systemd applied. A drop-in, a rejected property, or a unit started by some
    other path are outside what any fold over our own argv can see, which is
    exactly why the release predicate does not lean on the binding.

(2) HostRecordLost WAS ERASED BY THREE CONSUMERS and is carried through all of
    them now. compute_provide_dependent returned only the child's result, so a
    BUILD that lost its record followed by a successful dependent reported clean
    -- the status is joined onto the dependent's result, accumulating rather
    than letting the later arm win, because the two details name different
    identities. ProvideAttached carries the status too, so an attach cannot drop
    a carried loss. compile_entry_cli exits nonzero naming the loss, and
    run_selected_module carries it onto ModuleRan, into the emitted JSON, and
    into the module's verdict.

    None of this changes the stored work outcome: the ending and the document
    reported are exactly what was stored, and only whether the run is called
    clean moves.

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

Found by the srv1 fault-injection run for the re-attach control, and it is the
same defect as the consumption line once appended to outcome.json -- committed a
second time, one file along. Appending the host-record sentence to the receipt
stopped it being parseable JSON, so a consumer reading it back got "Extra data"
instead of an outcome.

A document a consumer parses carries exactly what its contract says. The receipt
is the document again; the loss is written to <receipt_path>.host-record and the
exit reason names where it went. Both CLIs.

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

review 69724, and it is this change's own diagnosis unapplied one boundary over.
gunbc.compute.host_capacity was calling kibibyte_count to STRIP its Kibibyte on
the way into SeatRequest -- the exact tell work_request.dag names for
HostBelowMemoryFloor and fabric_event_log.dag names for SeatRoomObserved.

A bare Nat was right while every pool reachable through this fold was
Pool<Count, One>: seats are dimensionless and the field meant seats. This PR
generalized the carrier to Pool<Q, S>, which made the same field mean KiB for a
memory pool and seats for a seat pool with nothing in the type to say which. So
SeatRequest is generic and amount carries Measure<Q, S, Nat>.

WHERE THE STRIP LEGITIMATELY HAPPENS IS ONE LAYER DOWN, and the difference is
not cosmetic: propose_acquire holds the POOL, so the pool's own type parameters
prove the request's dimension matches the appropriation it is adjudicated
against. A consumer stripping the carrier ASSERTS that match; this fold CHECKS
it.

THE PERSISTED EVENT KEEPS A BARE Nat AND THAT IS NOT THE SAME DEFECT, stated
rather than left to be re-found. PoolAcquired is a wire record: its dimension is
the partition's, and it is restored at both ends from the pool's own parameters
-- minted from a Measure the pool typed, and re-minted at replay by
pool_apply_event into the Pool<Q, S> being folded. A JSON integer carrying a
magnitude whose unit the surrounding type fixes is a serialization, not a second
model of the unit.

Call sites: harness_seat and fabric_quota wrap their counts, the capacity and
spark witnesses likewise, and host_capacity passes its Kibibyte straight through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 69724 finding 2. compute_with_lost_host_record matched the arms itself,
and the ProvideAttached arm returned the status it was handed while THROWING
AWAY the detail it had just built -- the exact vanish this function exists to
prevent, one arm along, in the function whose annotation is about a fact
vanishing.

It was latent only because the single call site passes a fresh result, which is
the kind of "safe today" that stops being true at the next call site. Two
spellings of one join is also the fork DESIGN 3 forbids, so there is now one:
provide_result_with_host_record, which already handled both arms and accumulates
rather than letting either side win.

The control drives BOTH arms. Mutation, run and reverted: restore the
arm-matching discard and a_lost_host_record_changes_no_contract_the_next_caller_reads
goes FAIL.

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

Ruled 2026-09-21 (loyal-swift-608): do NOT carry Measure through PoolAcquired
and the codec -- the decoder would still mint the caller-requested Q,S because
the wire has nothing to disagree with, so the RED would not be authorable
anywhere a check could run, which DESIGN 4b names as worse than absent. But the
error class the argument identified is real and nothing in the corpus holds it,
so it does not leave with the argument.

gunbc.recurring_failure_mode a_pool_partition_does_not_declare_its_quantity: a
persisted capacity partition carries magnitudes and does not declare the
QUANTITY they are denominated in. pool_apply_event mints every replayed amount
into the Q and S OF THE POOL BEING FOLDED, so a seat partition replayed by a
memory pool reads 1 seat as 1 KiB and a memory partition replayed by a seat pool
reads 27262976 KiB as 27262976 seats -- arithmetically consistent, semantically
unrelated, nothing refuses.

THE CLASS BECAME REACHABLE IN THIS PR, which is why it is filed here: generalizing
fabric_seat_acquire and fabric_seat_observe from Pool<Count, One> to Pool<Q, S>
is what made two quantities share one linearization. Before it, every partition
reachable through that fold was seats and the question could not be asked.

What is NOT claimed: that it has occurred. The two partition namers are disjoint
by construction today -- `quota-` over a rate-limit key, `compute-memory-` over a
host -- so the row is filed because the discipline preventing it is a naming
convention held by two authors, not a fact the substrate checks.

Trigger names the CAPABILITY -- partition identity binds the pool quantity, so a
mismatched fold refuses -- deliberately not "carry Measure through the codec",
which could land in full while the class stayed exactly as dead. Rostered, and
stated in the PR body beside the other declared gaps so a reader does not have to
find it in a failure-mode file.

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

recovery trigger

Not the finished package -- this is the spine that criteria 1, 2, 3, 4 and 6
rest on, with its controls. The provider is not wired to it yet (5 and 7 are
next), so nothing in PR 1's behaviour changes.

THE ATTEMPT SLOT. One durable CAS slot per attempt, keyed by the reservation
reference, on gunbc.durable_cas_file_store. Its value is the attempt record:
identity, reference, UNIT NAME, granted amount, launch deadline, state.

(3) THE LAUNCH IS CAS-GATED AND THAT IS ONE STEP. The producer advances
    Reserved -> Launching expecting the generation it read; recovery advances
    Reserved -> Terminal expecting the SAME generation. The store admits exactly
    one, and the losing PRODUCER has not spawned yet -- the refusal arrives
    before the launch, which is the difference between a gate and a regret. Both
    interleavings are driven on a real store: terminate-then-launch (producer
    refused, slot stays terminal) and launch-then-terminate (recovery refused,
    slot stays launching).

(2) The unit name is READ from the record the attempt wrote, never derived from
    the identity or a path spelling -- a derived name is a second authority for
    the binding, and a wrong derivation stops somebody else's unit.

(1) FIVE ARMS, NOT FOUR, and the count is a consequence rather than a target
    (ruling, loyal-swift-608). Folding authoritative absence into a failure arm
    would have spent a distinction PR 1 landed: "the manager has no record of
    this unit" and "the attempt failed" are different facts with different
    evidence, and only the first is safe to free a seat on WITHOUT an attempt
    record to consult. So TerminatedCauseUnknown carries it under a name that
    says what it is. Each arm's refusal is asserted individually.

(4) Unreadable is never absent, at the slot and at the record: an undecodable
    record is AttemptSlotUnreadable, and a state word this fold does not know is
    unreadable rather than defaulted -- a future state must not read as Reserved,
    which is the one state a launch may proceed from.

(6) Settling carries the verdict AND why, and the generation-suffixed store
    keeps what it settled: the reserved and launching generations are still
    readable after the terminal one lands.

THE GAP THE GATE DOES NOT CLOSE, AND THE TRIGGER THAT DOES. A compare-and-set
decides who WINS a race; it says nothing about whether recovery should have
entered one. A live producer that has not yet reached its CAS sits in Reserved --
exactly what an impatient recovery would terminate -- and the CAS then makes that
outcome look CORRECT: the producer refuses, releases, and the system appears to
work while a legitimate attempt was killed by a bystander.

So the trigger splits on the state, structurally rather than by policy. In
Reserved there is NO UNIT TO OBSERVE, so observation cannot distinguish a
pre-launch producer from a dead one -- both present the same absence -- and the
trigger is TEMPORAL, bounded by the deadline the producer itself declared when it
opened. In Launching the unit exists, so the trigger is the five-arm observation
with criterion 4 in full: only the three ended arms may terminate.

Eight wet controls pass against a real generation-file store.

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

Both gaps closed before the provider is wired to the trigger rather than after,
because both are about the thing that is about to become load-bearing.

THE DEADLINE IS DECLARED BY THE PARTY IT PROTECTS. A producer says what it needs
for checkout and argv assembly, and that same declaration is what stops anyone
reclaiming its seat -- so nothing stopped it declaring an hour. The cost does not
land on the producer: it lands on every other contender on the host as a seat
nobody may reclaim, which is DESIGN 5's externalization exactly -- an accepted
risk re-exported to another principal while the contract keeps its name.

Ceiling: 300 seconds, and it is an AUTHORED POLICY BUDGET, not a measurement.
What the window covers is one `git worktree add` of this repository plus argv
assembly; the srv1 receipts show whole attempts including checkout at about four
minutes, so five minutes for the checkout alone is generous by a wide margin and
still bounded. When a measured checkout distribution exists this row is
re-derived from it rather than defended.

IT REFUSES, IT DOES NOT CLAMP. Clamping would admit the attempt under a bound it
did not ask for and then reclaim its seat mid-checkout -- a widen wearing a
safety limit's clothes. The refusal names both numbers so the author learns the
ceiling instead of meeting it as a mysterious reclaim. A deadline that precedes
the opening instant refuses too: a window already closed is reclaimable the
moment it is written.

WHICH CLOCK, AND WHY IT IS SAFE HERE. The host's own epoch clock, and for two
reasons rather than convenience: the compute pool is host-local by construction
-- PR 1 placed its ledger on the host precisely because a host's memory is not a
fleet fact -- so both principals read one clock; and the pool's lease terms are
already denominated in these same epoch seconds, so a second source here would be
a second time authority for one quantity. The store offers no ordering to prefer:
its generations order TRANSITIONS, not elapsed time.

BOTH STEP DIRECTIONS NAMED, AND THE DANGEROUS-SOUNDING ONE IS BOUNDED BY THE GATE
RATHER THAN THE CLOCK. Forward makes recovery fire late: the seat stays charged,
the fail-closed direction, costing throughput. Backward makes it fire early
against a live pre-launch producer -- and that does NOT produce an unbacked
launch, because the producer's gate CAS then expects a generation recovery has
moved and it is refused before it spawns. The loss is the attempt, not the
invariant: a retry, not a seat handed to two holders. The deadline decides who
may ENTER the race; the compare-and-set decides who wins it, and only the second
is load-bearing for safety.

Nine wet controls pass, including the ceiling refusing at one second over and
admitting at exactly the ceiling.

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

that missed them

review 69769. Generalizing SeatRequest to SeatRequest<Q, S> left
gunbc.instruments.fabric_seat_probe (3 sites) and
gunbc.instruments.fabric_event_log_probe (3 sites) passing a bare `amount: 1`.
Both are operator-invoked entry points, so nothing in the required gate compiles
them and the breakage would have stayed silent until somebody ran the probe --
which is exactly the case DESIGN 3 legislates for: "for a root outside the gate,
enumerate the consumers by name before you delete".

THE CAUSE IS THE ENUMERATION, NOT THE EDIT. I did enumerate the consumers, and
then piped the enumeration through `head -12` and acted on what survived the
truncation. A capped list is not a population; it is a list that has already
agreed with whatever I was about to do. The re-run is unfiltered and uncapped:
six construction sites, all six now carrying Measure { count: 1 }, and zero bare
ones left anywhere in dag or src/v2.

The PoolAcquired wire record in fabric_event_log_probe deliberately keeps its
bare Nat, for the reason already recorded on that field: its dimension is the
partition's and is restored at both ends from the pool's parameters.

Verified: both instrument closures compile with 0 blocking errors, which is the
check that did not exist for them before -- nothing routinely compiles these, so
the compile is the enumeration's receipt rather than a formality.

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

Asked for by the owning manager after review 69769, and filed for the SHAPE
rather than the instance: the invalid state is a census whose denominator was
silently reduced by the reading tool and then used to justify a deletion or a
re-signature.

THE SHARP EDGE IS THAT IT DOES NOT FAIL RANDOMLY. A cap fails toward the
consumers nothing else would catch: a module with few mentions sorts to the
bottom and is cut first, and having few mentions is exactly what makes it
unlikely to be caught by any other route. The arms the cap hides are POSITIVELY
CORRELATED with the arms that matter.

WHY IT SURVIVES REVIEW: incompleteness is a property of the READING, not of the
diff. Every migrated site is correct, the typechecker is silent because the
missed consumers are outside the gate's closure, and the enumeration itself is
not an artifact anybody reviews -- it happened in a shell and left no trace.
Re-running it uncapped is the only thing that finds it.

DISCRIMINATOR, phrased as a question about the enumeration rather than the diff:
was the enumeration that justified this change itself UNBOUNDED? Not "did you
check the consumers" -- the author of the specimen did check them.

WHY IT IS NOT truncated_local_diagnostics_indistinguishable_from_the_complete_set,
stated because the two look alike and the manager asked me to grow a row rather
than split a class: there an ORACLE DIED mid-measure and nobody chose the
truncation, so the discriminators are out of band -- an abort line, a missing
receipt, a stack limit. Here the truncation is CHOSEN, by the reader, as a
display convenience, on a population the reader is about to act on; there is no
abort to notice and no receipt to be missing. Same silence, different cause,
different discriminator, different remedy.

TRIGGER NAMES THE CAPABILITY: a change that alters or removes a declaration is
checked against the declaration's full consumer set derived from the namespace
tree, consumers outside the required gate included by construction. Deliberately
not "do not use head" -- a habit is not a wall, and the class would stay exactly
as alive under a different paging tool.

Filed on the lifecycle branch rather than PR 1, to keep PR 1's head still.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 69784. meminfo_line_metric took token 0 as the key and token 1 as the
count and DISCARDED token 2 -- the `kB` the module's own annotation leans on.
/proc/meminfo is not uniformly kB: four lines on every Linux host
(HugePages_Total, HugePages_Free, HugePages_Rsvd, HugePages_Surp) carry NO unit
because they are counts of PAGES, verified on this machine -- 54 lines, 4 without
a unit, exactly the four named. Minting those as Kibibyte is wrong by the huge
page size, 2048x at 2 MiB, and nothing in the type says so: a MemoryMetric
carrying 0 pages and one carrying 0 KiB are indistinguishable.

The seven named fields are all genuinely kB, so the admission arithmetic was
never affected. What was wrong is the surface all_metrics and
meminfo_metric_named expose to the next consumer.

AND THE WITNESS LOCKED IT IN: count(all_metrics) == 8 against a fixture whose
eighth line is `HugePages_Total: 0` -- it asserted the non-kB line WAS admitted.
That is the second control in this change to pin the behaviour it existed to
refuse, after the one that asserted completed+unknown frees a seat. Both were
mine and both were written the same way: state what the code does, then assert
it.

THE UNIT IS NOW REQUIRED AND COMPARED, not discarded, and all_metrics is named
for what it is -- the kB-denominated metrics, not every line. A page count is a
different quantity and would need its own carrier; that is a row to add when a
consumer exists, not a field to fake now.

TWO FIXTURES BECAUSE TWO DIFFERENT PARTS OF THE FOLD EXCLUDE THEM, and finding
the second is the reason the mutation was worth running. A UNITLESS line is
excluded by the arity requirement. A line with the WRONG unit is excluded only by
the comparison -- and nothing in /proc/meminfo authors that shape, so without a
fixture the comparison was a permanently green decoration. Mutating the
comparison away left the cell GREEN; with `SomeFutureField: 64 MB` added it goes
FAIL. Restored and green again.

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

Side-chat verdict on #11962: one cited-module-absent, reported by the floor's
declaration phase. a_held_compute_seat_has_no_in_corpus_discharge cited
"test.claim.compute.work_request_witness"; the module declares itself
"test.claim.compute.work_request_witness_test". The declaration it names is
real and is in that module, so this is the string and nothing else.

THE TRAP IS A NAMING INCONSISTENCY AMONG SIBLINGS, worth recording because the
next citation will meet it too: host_capacity_wet_witness_test.dag declares
`module test.claim.compute.host_capacity_wet_witness` and
os_proc_meminfo_witness_test.dag declares
`module test.claim.os_proc_meminfo_witness` -- both DROP the _test -- while
work_request_witness_test.dag KEEPS it. A citation written from the pattern
rather than from the file is wrong for exactly one file in the directory.

So I audited rather than fixed the one: every module_path and decl_name in both
rows this change adds, resolved against the real `module` lines and declarations.
All resolve.

It predates the approved baseline bf58075 -- no delta commit introduced it -- but
it is a live declaration refusal inside this package, so it is fixed here rather
than deferred. The census-image wet refusal is disposed nonblocking by the owning
manager and is not touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two findings arrived at independently by two sessions within an hour, which is
what makes the class worth a row rather than two apologies.

a_control_that_shares_its_derivation_with_its_subject: a control whose content is
DERIVED FROM the thing it is meant to discriminate cannot disagree with it. Two
routes, and neither author recognised the other's until they were compared --
DESCRIBING (read what the code does, assert that) and COPYING (inline the
production body with one input swapped). Copying is just the most literal way of
writing a control from the implementation.

Specimen one is mine, twice in one change: a meminfo witness that asserted the
non-kB line WAS admitted, and a termination control that asserted
completed-plus-unknown frees a seat. Specimen two is loyal-swift-608's on
gunbc#12017: a RED carrying the production door's body inlined, so it evaluated
its own copy of the test it existed to guard, with an annotation above it
asserting the opposite in as many words.

THE SMELL THAT NEEDS NO RUN, which is their contribution and is cheaper than the
mutation: ASK WHAT THE CONTROL SHARES WITH ITS SUBJECT. If it re-derives,
re-implements or re-describes the thing under test it is suspect before any
mutation is attempted. The mutation is the proof; the shared-derivation question
is what says to go looking.

AND THE REPAIR HAS ITS OWN FAILURE MODE, which is the more transferable half: the
first fix of specimen one added the missing unit comparison, and mutating that
comparison away left the control STILL GREEN, because a unitless line is excluded
by the ARITY requirement and /proc/meminfo publishes no wrong-unit line at all.
THE FIXTURE POPULATION HAS TO CONTAIN THE CASE THE CHECK EXISTS FOR -- and a
real-world corpus is exactly where that case tends not to occur, which is why the
fixture is where it must be authored. Boundary against
predicate_vacuously_true_on_an_empty_domain stated in the row: there the domain is
empty, here it is populated and healthy-looking and merely lacks the one case.

Also into an_enumeration_capped_by_its_reading_tool, a statement of the rule
sharper than either the manager's or mine (reviewer on gunbc#11962): PRESERVE THE
COMPLETE CONSUMER SET AS THE MIGRATION INPUT, AND CHECK EVERY AFFECTED OUT-OF-GATE
ROOT FROM THAT SET; TRUNCATE ONLY ITS DISPLAY. That splits the legitimate cap from
the illegitimate one, which neither "never truncate" nor "do not use head"
manages: a cap on what you LOOK AT is how anyone reads a long list; a cap on what
you ACT FROM is the defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lease-drop contracts become one

(5) RE-CHECK UNDER THE LEASE, and the instance is concrete rather than
    ceremonial. The stored-outcome attach was read BEFORE the producer lease
    existed, which makes it evidence about a past world: between reading "no
    stored outcome" and winning the lease, another producer can finish and
    publish one. The first caller then re-produces work already done AND takes a
    second reservation for an identity already satisfied -- the dedup the
    contract promises, lost to a window nobody held anything across.

    The observation is now re-taken while holding the lease and THAT one is the
    decision. The early read is kept as an optimisation, so the common case --
    an identity already built -- still answers without touching the lease at
    all, and the annotation says which of the two decides.

(7) ONE LEASE-UNDROPPED CONTRACT, because there were THREE and they already
    disagreed. compute_refused_unreserved turned a failed drop into a verdict
    about the work (StoreUnavailable); compute_run_and_settle folded it into the
    consumption note and did not; and the attach path added by (5) would have
    needed a third. Three places deciding one fact is the DESIGN 3 fork, and the
    thing to delete rather than to keep consistent by hand.

    The contract, stated once and applied at every site: a lease that could not
    be dropped NEVER changes what the work did. It is a host-operational fact, it
    rides as a HostRecordLost so the caller hears it without the stored record
    being contradicted, and the remedy is an operator removing the name.
    compute_drop_producer_lease is now the only caller of Filesystem.Delete on
    the lease path.

Also carried here, on the owning manager's instruction not to push it to PR 1:
the duplicated annotation fragment review 69814 noted at work_provider_local
line 783. Pushing a comment typo to PR 1 would restart CI and invalidate an
APPROVE that already sits on its head -- the whole tally for one character class
of edit.

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

# Conflicts:
#	dag/gunbc/compute/work_provider_local.dag
…or out

The gate is now ON the real route: reserve, open the attempt, check out, ASK,
spawn. The ask sits last because the window the launch deadline protects IS the
checkout -- with it immediately after the open, the two compare-and-sets are
microseconds apart and the interleaving the gate exists to refuse cannot occur
on the real route, so the wall would have been permanently green by
construction.

compute_recover_cli is the discharge the seat had no route to. It reads the
attempt slot, observes the unit through the manager and its cgroup, evaluates
the recovery trigger, and commits the terminal generation EXPECTING the
generation it read -- so a producer that advanced meanwhile wins and the
recovery is refused by the store rather than by noticing afterwards.

unit_population is narrowed to the two readings it uses. Recovery previously had
to construct the producer's whole observation record, which carries
attempt_completed -- a first-hand fact recovery cannot have. Recovery now has no
carrier for first-hand completion anywhere on its route, so the distinction is
unwritable rather than unwritten.

RecoverySettledButHeld separates the terminal transition landing from the seat
coming back. A recovery whose release the ledger refuses closes the attempt and
leaves the memory charged, and says so with a nonzero exit.

a_held_compute_seat_has_no_in_corpus_discharge records its trigger as fired and
its rung as climbed rather than being deleted, and the refusal text that pointed
at an unbuilt capability now names the door.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dropped contract gets a control

The consumption receipt's new "attempt:" line was rendered with host_record_wire,
which returns the empty string on success because it is built to be appended to
a sentence. The first real srv1 run at the wired head printed "attempt: " with
nothing after it. A line that says nothing on the good path cannot be read as
evidence the good path happened, which is why the line exists.

work_request_witness_test was still calling compute_with_lost_host_record with
the pre-refactor dropped_ok/dropped_error pair, so the file did not typecheck on
this branch at all. It now takes the LeaseDrop carrier, and criterion 7 gets the
control it was missing: an undropped producer lease is recorded, never becomes
the work's verdict, and its note does not tell the operator something the
contract contradicts.

The AttemptState annotation now names what ENFORCES the entailment the temporal
trigger rests on: compute_spawn_and_collect is reachable from exactly one place,
so a slot reading "reserved" means nothing was ever spawned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc-ci-auto-heal and others added 21 commits September 22, 2026 02:57
WorkCancelled was a bare identity. Its only producer is a launch refused by the
attempt gate -- somebody else ended this attempt before it spawned -- so a
caller reading an ending of "cancelled" with no cause could not tell that apart
from any other cancellation, and neither could the next reader of the stored
document. The detail now rides in the durable outcome like every other arm's.

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

an_undropped_lease_is_recorded_and_never_becomes_the_works_verdict was RED at
this head on one conjunct: it asserted the stale-lease note contains no
"ProducerInFlight" at all. The repair that note actually received was to SCOPE
the sentence, not to delete the word -- a caller reaching a published success
attaches before the name is consulted, and the name still refuses a request
that must PRODUCE again, which is true and worth telling the operator. So the
claim forbade more than the contract requires (DESIGN 4d, over-prohibition) and
went red against correct text.

The assertion now names the two clauses that carry the scope, so a note that
drops them and states the refusal unconditionally is still red, while the word
itself is free to stay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he record, bare numbers only at the JSON boundary, and one host clock

Review 69915 on gunbc#12040 found two sites; the sweep found the population.
granted_kib: Int stripped the carrier the producer held (work_request's own
note names the defect); compute_now_seconds reproduced
gunbc.fabric_event_log_host now_epoch_seconds body-for-body while the
annotation beside it named that authority. Both were annotations written from
the right model with the code going the other way. The record now holds
granted: Kibibyte and EpochSecs instants; RecoveryTooEarly and recovery_trigger
take EpochSecs; the decode re-admits an instant and a grant (negative is
unreadable, not cast); the Int? clock fork is deleted and both callers consume
now_epoch_seconds, as host_capacity already did. granted was never read for a
decision -- only serialized and re-decoded -- so this was a DESIGN 3 fork, not
a live miscalculation.

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

Two items, both from review 69915's population rather than its two sites.

THE SWEEP'S THIRD INSTANCE. compute_pre_launch_ceiling_seconds and
compute_launch_window_seconds carried the unit in the field NAME on a bare Int,
which is the same defect finding 1 named: modeled twice, in the name and in the
rendered text, and nowhere the compiler can see. std.types Seconds is the
carrier and is consumed corpus-wide -- gunbc.auth.access_request holds the
identical ceiling/requested shape -- so both constants take it.

THE ROUTE GAP, which is not a code defect and is answered by the contract home
its own package already used. The hermetic route has no arm for
shell.Mktemp.DirWithTemplate, so the floor plans this witness and it reaches no
subject. gunbc#11962's host_capacity_wet_witness_test.dag admits exactly this
effect and is disposed of by a pair: a gunbc.ci.ci_layer_roots
WitnessExclusionRow under excl_local_repo_wet_tempdir_write_reason, and every
one of its functions on v2.workflow.local_repo_wet_terminal
local_repo_wet_schedule. This file takes the same pair. Not a floor_route_gap
enrolment: the exclusion keeps the identities out of the hermetic discovery
corpus rather than recording a gap as held debt, which is what the neighbour
does and what the effect warrants.

ALL NINE FUNCTIONS ARE ENROLLED, NOT THE GAPPED SUBSET, and the two
measurements are the argument. The #12040 CI run named five identities; a
hermetic claim_batch over this tree ends six that way and reaches three
verdicts. Which claims get a verdict hermetically is a property of the
evaluation order, not of the claim, so a subset roster would be right today and
wrong after an edit nobody connects to it.

Measured wet over this tree before enrollment, which is the receipt the
roster's own idiom requires: attempt_lifecycle_wet_witness 9/9 and
work_request_witness 10/10 PASS, 19/19, no FAIL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ause is printed and the token still says FAIL

The checkable question was whether the six route-gap lines carried their cause.
THEY DID, verbatim: "FAIL <fn> (hermetic route has no arm for DirWithTemplate:
operation declares no mock_response -- the claim never reached its subject, so
this is a route gap and not a verdict)". So this is not the reporting layer
distinguishing a gap on one path and conflating it on another; claim_batch says
in words that the line is not a verdict. It says so AFTER the verdict token,
which is the part worth filing: grep -c ^FAIL counts six reds, ^PASS reports
three of nine, and the same nine are 9/9 with --wet over the same tree.

FILED AS A RECEIPT ON AN EXISTING ROW RATHER THAN AS A NEW CLASS.
gunbc.recurring_failure_mode an_unreached_measurement_renders_as_a_failed_one
already owns this invalid state -- an unreached subject presented where
adjudicated-and-lost is meant -- and a second row would be the section 3 fork
the ledger exists to avoid. Only the grain moves, from a required lane at the
merge surface to one identity inside a batch run.

The two specimens bracket the remedy, which is why the second is worth keeping
beside the first. The original has the distinction TYPED in a standing field the
deciding surface never reads. This one has it RENDERED where every reader sees
it and no reader can act on it mechanically. Neither is fixed by emitting more,
which is what that row's trigger already says: the surface has to consume it. For
a per-identity line the surface IS the token, so the remedy here is a token of
its own, not a better parenthetical.

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

Per the parent's point on 9d9f074. The row said the cause was printed and left a
reader to infer what follows from that. It now says it outright: this is NOT the
harness conflating a route gap with a verdict -- the cause is computed, correct
and rendered -- and what fails is that it is carried under a token that reads as
a verdict. The remedy is therefore a distinct terminal, not more annotation, and
an instruction to read the parenthetical is a mitigation carrying exactly the
dependency on a human knowing to look that the row's own RUNG note names.

Held out of the push that is currently building: a push cancels the in-flight
floor run, and this is prose on a data row.

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

Raised on the #12040/#12044 merge. Append-against-append resolves as a union,
which is right when the two appended rows are independent and silently wrong
when both sides appended the SAME identity -- git cannot see that, because both
sides did the same legal thing, so the merge produces a green tree carrying a
duplicate. The read that cleared that merge was an eye over 220 rows.

NEITHER SIBLING CLAIM CAN SEE ONE, and the second is the interesting half.
every_roster_row_spells_its_function_the_same_way_twice reads WITHIN a row. And
the_local_roster_is_not_empty compares length(identities) == length(schedule),
which is a map -- a map never changes a list's length, so that equality holds
over a roster with a duplicate in it. It reads like a completeness check and is
structurally incapable of being one.

MEASURED RATHER THAN ARGUED. Duplicating one row in the real schedule:
no_two_roster_rows_name_the_same_identity FAIL, the_local_roster_is_not_empty
PASS, every_roster_row_spells_its_function_the_same_way_twice PASS. Restored
tree: all three PASS. So the claim is wired to the roster and the reading above
is the run's, not mine.

the_duplicate_identity_predicate_discriminates is the second control and covers
what the mutation cannot: the roster claim is a filter returning zero, and a
filter over a predicate that never fires returns zero too. Two supplied lists
differing only in whether one name repeats separate those.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 69967, and it is fabricated plausible output on a reachable path rather
than a modeling point. compute_run_leased returns AttemptGateRefused when the
launch CAS loses, compute_run_under_grant writes compute_attempt_settlement_wire
into the consumption receipt, and the refusal mapped to HostRecordWritten -- so
the operator-facing record of a LOST launch race asserted this producer settled
the attempt slot, having settled nothing and observed nothing about the slot's
final state. DESIGN section 5.

THE CAUSE IS A THREE-VALUED FACT ON A TWO-ARMED CARRIER, so the third case had
nowhere to go but the success arm. The fix is the third arm, and the argument is
about WHOSE TYPE it belongs to. It is not on HostRecordStatus: that answers one
question -- did this host record its own side facts, or is there a loss an
operator must chase -- for five producers, and `nothing was owed here` is not an
answer to it for any of the others. The arm would be uninhabitable everywhere but
this call and would owe host_record_join and host_record_was_written a rule with
no meaning to carry. So settlement gets AttemptSettlement (Settled / NotSettled /
Unsettleable) and a total projection into the shared carrier.

UNSETTLEABLE PROJECTS TO HostRecordWritten DELIBERATELY. A loss means this host
owed a record and failed to write it, which is an operator action. A refused gate
owed nothing: it never held the slot. Mapping it to HostRecordLost would
manufacture an incident -- the same fabrication as the success arm, pointed the
other way.

CONTROL, driving the REAL race rather than describing it: two producers open
against one store, the winner advances the generation, so the loser's
begin_launch is refused BY THE STORE. PASS. Mutation restoring
`AttemptGateRefused => AttemptSettled`: FAIL, on a clean run. An earlier mutation
attempt was killed and is reported nowhere, because the tree was restored while
it executed -- a run whose input changed under it has no verdict.

ROUTE-GAP ENROLMENT, which is the other half of the CI red at 9d9f074. The
exclusion row keeps this file off the floor on every run that does not touch it;
a CHANGED witness is selected regardless of discovery exclusion, so the PR that
edits it plans the identities anyway. floor_route_gap_expectation_chunk_21
enrols exactly the six that gapped, and the row names what re-observes it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/v2/workflow/floor_route_gap.dag
Round four on gunbc#12040, verified at head d2be498 before acting on it.
compute_recover_attempt occurs exactly twice in the tree: its definition, and one
call from compute_recover_cli. The only test-side touch is a string_contains over
the remedy TEXT, and DESIGN section 5 rules in those words that a typecheck and a
.contains() grep are not consumers. The wet witnesses reach attempt_open,
attempt_begin_launch and attempt_terminate; none reach this door. So the row's own
trigger -- the capability CONSUMED BY A REAL CALLER -- was marked discharged while
its own words went unmet.

CURRENT RUNG GOES BACK TO MITIGATABLE, stated as a correction rather than left
implied by the absence of a climb. Nothing regressed: the door is as good as it
was and the evidence for it never existed. Section 4b(1) is the governing
sentence, and rung inflation is worse than sitting low because an inflated class
never ranks for climbing -- here the class that would stop being asked about is a
compute seat nobody can reclaim.

THE TRIGGER IS A CAPABILITY AND NOT A CHORE, because the control cannot be written
today at any price. compute_recover_attempt takes a HostDashboardInstance and
derives its attempts root from it, so no fixture can point the door anywhere but a
real instance's real compute root -- a wet control over it would drive production,
not the tempdir the harness already builds for every other attempt-lifecycle
claim. The trigger is therefore a door whose STORE IS A PARAMETER, sufficient for
an executing control to drive a held seat through discharge against a temporary
store. Naming `write a test` would have been a trigger nobody could satisfy.

The annotation at the door said the same thing in the second place and is
corrected too. Leaving it is how the row got re-inflated once already.

Also merges origin/main. The route-gap roster conflicted in the shape that is
worth recording: main landed the SAME fix for host_capacity_wet_witness -- its
header says gunbc#11962 shipped two of the three rows and the five identities
blocked the merge queue for gunbc#11829 -- and both sides named the new function
floor_route_gap_expectation_chunk_21. The two `chunks()` lines were textually
identical, so git merged that line clean and left TWO definitions with ONE call
site. Mine is renamed chunk_22 and both are registered; the enrolled identities
were checked for duplicates separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hould have said so was a count

Review 70036 and the floor at 3a60e2c agree, which is as confirmed as a finding
gets: the wet witness file holds TEN test fns and nine were enrolled. The tenth,
the_loser_of_a_launch_race_never_reports_that_it_settled_the_slot, appeared only
at its own definition -- and git grep AttemptUnsettleable returns exactly two
files, so it is the SOLE EXECUTING CONTROL for the arm added in d2be498. The
fabricated-settlement finding had been answered with a control that does not run,
which is 4b(1)'s inert-lens case with the sting that it LOOKS like evidence in the
diff. CI reported it as route_gap AND
changed_witness_planned_without_terminal_verdict.

ENROLLED IN BOTH ROSTERS, and the route-gap row is a PREDICTION rather than an
observation -- a departure from that roster's own rule, said in the row. The
claim's first statement is fresh_root(), which is DirWithTemplate
unconditionally, so it cannot reach its subject hermetically under any order.

THE CENSUS IS NOW A RULE, NOT A COUNT. It said NINE ATTEMPT-LIFECYCLE MEMBERS
over a file that had grown to ten, and that is how the gap stayed invisible.
Changing NINE to TEN would reproduce it on the next addition, so membership is
stated as every test fn in the file, with the admitting property being the
EFFECT. Same class as the route-gap roster's six-against-nine, in the other
direction: there the honest form is an observation, here it is a policy.

THE ROSTER-UNIQUENESS CLAIM IS LIFTED OUT, and the reasoning stays behind in the
module so nobody pays for the wall twice. It costs 346735 eval steps against a
72300 new-witness budget, the overage is the pairwise comparison itself, and no
construction here makes it linear -- the substrate has no set and no sort over
String. A declared ceiling or a reduced subject are both decisions about a shared
budget and belong to their own change, not to a compute-lifecycle PR four rounds
deep. What it guards is real and unguarded: a duplicated schedule row survives a
union merge silently, wet_forward_causes walks SCHEDULED so it finds the same
terminal twice with n == 1 and raises nothing, and both sibling claims are
structurally blind to it.

AND THE MEASUREMENT FALSIFIED MY OWN REPAIR, which is the part worth keeping. I
read the first version as rebuilding the roster projection inside the inner
predicate and hoisted it. The figure moved 343406 -> 346735, the difference being
one row the roster gained. The rebuilt projection was never the expense, and the
structural story I told about it was wrong.

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

CI on 9eb96c4: 20 parse errors, one block, all 'source annotation names no
subject: no module item follows it'. The REQUIRED-FLOOR refusal
ArmSetConsumerPlanningUnavailable in the same run is downstream of the parse
failure rather than a second defect -- no parse-phase declaration index reaches
the floor when the module will not parse.

The block is the reasoning about the roster-uniqueness claim this change lifted
out. It went at the end of the file, and annotations are module-item grain: a
block at EOF has no subject. It now sits above the roster claims it is about,
which is also where a reader looking for the missing check would start.

The indented-annotation grep that has been circulating passes this cleanly,
because the block is at column 1. Both shapes need checking:

  grep -Hn '^[[:space:]]\+//' <file>
  awk '/^[[:space:]]*\/\//{c=NR;next} /^[[:space:]]*$/{next} {c=0} END{if(c) print FILENAME": trailing annotation at "c}' <file>

Both now run clean over every file in this diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
compute_release_on folded SeatNotHeld into ComputeReleaseRefused through a
catch-all arm, so a double release (recovery then producer) wrote outcome.json
as STILL CHARGED with the stuck-reservation remedy. ComputeNothingToRelease is
now its own arm, the three pre-grant arms use it, and every SeatRelease arm is
named. The new wet claim releases one real seat twice and is enrolled in
local_repo_wet_schedule; it holds, and goes red when SeatNotHeld is routed
back to the refusal arm.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… no annotation is admitted

Moved to the fn it explains (DESIGN §4c: // attaches only at module-item grain);
emit-build refused it as 'source annotation sits inside a declaration body'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…state words accepted it

Review 70244. AttemptTerminal.verdict is now AttemptVerdict = AttemptWorkEnded
{ ending: WorkEnding } | AttemptRecovered -- the two writers the slot has. WorkEnding
types the ending words work_outcome_ending already spelled (it now projects through
work_ending_wire, so there is one spelling). attempt_state_decode refuses a verdict
outside the set; RecoveryStateNotRecoverable carries the AttemptState. The witness's
'released' was never a word any producer wrote; it now uses real verdicts.

Control a_terminal_verdict_outside_the_closed_set_is_unreadable (enrolled in the wet
schedule) round-trips all six verdicts through the real record wire and decodes
'recoverd', 'released' and '' as unreadable. All 11 lifecycle claims hold; widening
an unknown verdict to Recovered turns the new claim false.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ted settlement (#12044)

* A manager that cannot be asked is a property not queried: ShowUserProperty opts into ShellOutcome, and the two unit readings fold it before the fold decides

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

* The check that replaced a decoration was a decoration too, and only the mutation said so

Review 69970, both findings, plus one the review did not name and one my own
first repair introduced.

FINDING 1, the borrowed exclusion reason. The row reused
excl_shell_spawn_refused_reason, which substantiates a DIFFERENT subject in its
own words -- whether the host can spawn argv[0], one effect, a local program.
This witness's subject is a systemd user manager, four unit properties and a
cgroup read. DESIGN 3b: a home that resolves but owns only part of the scope a
row claims is a scope mismatch on the row. It now has
excl_manager_unaskable_reason and its own dissolution, which also states what a
PARTIAL mock does not discharge. Returning the borrowed row repaired its own
sentence: `this row and its four schedule rows delete together` is true again.

FINDING 2, the decoration. On the one lane this cell runs (srv1, systemctl
present) the claim was `if present { true }` and the pre-change behaviour was
identical, so nothing on the executing path could turn it red. The subject was
FUSED TO THE EFFECT -- observable only where systemd is absent. unit_property_
from_shell is now its own fold, so both ShellOutcome arms are authorable
anywhere, and the wet claim keeps the inhabitance half with something real to
assert on the present-manager branch.

THE THIRD PROBLEM, WHICH IS FINDING 2 ONE MOVE ALONG. The new hermetic claim was
sitting in a DISCOVERY-EXCLUDED file, so it would have executed on the change
that wrote it and never again. Exclusion silently converts `always runs` into
`runs when this file changes`, which is invisible at the call site and reads as
coverage. The fold's claim moved to work_request_witness, discovered every run;
the wet file keeps only what needs a host, and says why the split is where it is.

THE FOURTH, AND IT IS MINE. The relocated claim PASSED against a fold mutated to
`ShellSpawnRefused => { queried: success, value: value }` -- the wrong arm that
reads the flag instead of the outcome. I had supplied that arm with success:
false and an empty value, so the correct fold and the wrong one produced the SAME
reading and the claim carried no information. Right interface, right layer, right
assertions, zero discrimination. The separating input is a refusal with a
FLATTERING FLAG -- success: true, value: "active" -- because the contract is that
the OUTCOME decides: a spawn that produced no process is not a reading whatever
the transport's other fields say.

CONTROLS at this head: mutation FAIL, restored PASS, and the wet inhabitance cell
PASS. The reasoning about the inputs is in the claim rather than here, because the
next person to edit them is the one who needs it.

Also merges the current gunbc#12040 head. Chunk names did not collide this time
(21/22 against 23) and all four checks ran: no duplicate name, 24 definitions
against 24 registered members, no duplicate identity, module resolves.

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

---------

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>
…otation is admitted

Hoisted above the test fn (DESIGN 4c). Swept every .dag file in the PR's
diff for an indented // line; this was the only one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…enrolled DirWithTemplate route gap

releasing_one_seat_twice_is_not_a_ledger_failure_and_charges_nothing landed with its
WetScheduledClaim row but no floor_route_gap row; floor job 106999407163 read
route_gap_unenrolled=1 while the local-repo wet lane passed it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ough the attempt gate

The retry opens its own slot under compute_ceiling_retry_reference and asks before spawning; the first
attempt is settled in its slot before the retry opens. ComputeAttempt carries the gate (LeasedRun
folded into it). Imports are the union. Adds a wet witness that a held retry slot refuses the retry
before spawn.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ject's request

main (#12021) gave compute_capacity_subject a request argument; the recovery path from #12040 still
called it with one, and the floor refused the prepared subject on that call shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mplate route-gap row

The floor at bf63e3c reported it as an unenrolled route gap (fresh_root is DirWithTemplate); the
file's LocalRepoWetLane exclusion already covers it by pattern.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls marked this pull request as ready for review September 23, 2026 07:40
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T07:44:41.190407Z a308481 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a308481b50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +631 to +632
} else if trim(s: o.active_state.value) == unit_active_state_failed_wire {
UnitTerminatedFailure { detail: join(["ActiveState=failed, Result=", if o.result.queried { trim(s: o.result.value) } else { "<unreadable>" }], "") }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the seat when failed-unit population is unknown

When ControlGroup or cgroup.events cannot be read, o.population is PopulationUnknown, but this branch still treats ActiveState=failed as a confirmed termination. A failed systemd unit can still have live processes during shutdown, so recovery then advances the attempt to terminal and releases its memory reservation without establishing that the cgroup is empty. Require PopulationEmpty before returning an ended lifecycle for this state; an unknown population must remain UnitLifecycleUnknown.

Useful? React with 👍 / 👎.

match recovery_trigger(record: rec, lifecycle: lifecycle, now: now) {
RecoveryTooEarly { deadline: d, now: n } => RecoveryDeclined { reference: rec.reference, why: recovery_trigger_wire(t: RecoveryTooEarly { deadline: d, now: n }) }
RecoveryUnitNotEnded { lifecycle: u } => RecoveryDeclined { reference: rec.reference, why: recovery_trigger_wire(t: RecoveryUnitNotEnded { lifecycle: u }) }
RecoveryStateNotRecoverable { state: st } => RecoveryDeclined { reference: rec.reference, why: recovery_trigger_wire(t: RecoveryStateNotRecoverable { state: st }) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow terminal recoveries to retry the seat release

If recovery commits the terminal generation but crashes before compute_recover_release, or if that release transiently returns ComputeReleaseRefused, the slot remains terminal while the seat remains charged. Every subsequent invocation takes this branch and declines without retrying the release, making the leak permanent despite RecoverySettledButHeld reporting that the ledger still needs to accept one. A terminal AttemptRecovered record must retain an idempotent route to release its recorded reference.

Useful? React with 👍 / 👎.

@gunbai-bot

gunbai-bot Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Duplicate auto-opened on close of jolly-ibex-659: same branch and head (a308481) as #12040, which already merged. Nothing to land. — sent from loyal-swift-608

@gunbai-bot gunbai-bot Bot closed this Sep 23, 2026
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