Skip to content

FLEET-RESOURCE-0A: one runner-width producer, the memory-per-vCPU convention read from the survey, CtrlBuild deleted - #9773

Merged
gunbai-bot[bot] merged 13 commits into
mainfrom
session/eager-ram-258
Sep 1, 2026
Merged

gunbai-bot[bot] merged 13 commits into
mainfrom
session/eager-ram-258

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

First stage of FLEET-RESOURCE-0 (gunbc-owned runner and compile-resource convergence). Model only — no host mutation, nothing converges to a host.

1. One runner-width producer

RunnerHostDeployment.runner_count consumed gunbc.fleet_host_budget declared_runner_count_for while gunbc.runner_slot_allocation gunbc_runner_slots_per_host (the min over memory/CPU/disk) was the documented derivation.

Correction to an earlier version of this body: those two did not disagree. gunbc_runner_pool_budget_for = slots × memory_max and gunbc_ci_runner_cgroup_memory_cap is memory_max, so declared_runner_count_for = floor(slots·k ÷ k) = slots. It was a semantic round trip, not a competing authority. The deletion is behaviour-preserving.

Underneath sits a §3 nickname worth naming: gunbc_runner_pool_budget_for is named as supply and computes demand. The genuine supply function runner_slice_cap(host, overhead, session_slice, headroom, compile_pool) lives in the same module. That nickname is why host_memory_admitted_width is an authored literal table — the memory axis has no supply-side denominator, and deriving one from the budget would close a loop, since the budget derives from the width. Repairing it needs the derivation hoisted above runner_slot_allocation. That's FleetHostResourceAllocation, not this PR.

2. The 1:3 ratio is surveyed, not authored

byte_size(3000000000) deleted; gunbc_runner_memory_per_core() reads memory_per_vcpu from gunbc.runner_shape_census against the Ubicloud aarch64 catalog.

GitHub Actions is deliberately not a second source, and the census says so rather than this PR asserting it: GHA's aarch64 catalog reads 4/vCPU where uniform and carries two non-integral sizes (arm-5core at 14, arm-64core at 208), so memory_per_vcpu refuses on it. Averaging the survey would mint a figure no vendor publishes.

Correction — an earlier revision of this section and its annotation were wrong on both halves, and the error was load-bearing. They claimed no vendor page states whether its "GB" is decimal or binary, and adopted gibibytes on that basis, citing an operator ruling of 2026-08-30. There is no such ruling; I wrote it. And the basis was stated: extdeps.cloud.ubicloud quotes its catalog as QuotedDecimalGigabytes. What discarded it was the census — per_vcpu_magnitude rebuilt every ratio as QuotedWithoutBasis, so each consumer had to re-guess an answer the rows already carried. See §7.

3. A zero-memory slot shape now refuses explicitly

The identity in §1 holds only for memory_max > 0 — measure_fit_count_floor returns 0 at a zero divisor. So the deleted round trip was carrying a zero-cap refusal by accident. The derived path would still refuse (cores_per_slot → 0 → ZeroRunnerWidth), but gunbc_runner_slots_per_host consults host_held_runner_width first, and srv2's held 5 never looks at the slot shape. RunnerSlotShapeStanding states it, checked before the held arm.

The RED is authorable because the standing takes the shape as a parameter — gunbc_runner_slot_desired() is fixed, so without that parameter the check would be permanently green by construction and cited as coverage while proving nothing. Positive control included.

4. The per-slot CPU ceiling reaches systemd

cores_per_slot decided how many units to create and then vanished. Measured on the hosts: srv1 and srv2 carry MemoryMax as their only resource line — CPUWeight [not set], CPUQuota infinity, AllowedCPUs empty. The CPUWeight=100 in systemd_unit_file_witness_test is on no host, and wouldn't be a ceiling if it were.

extdeps.systemd gains CPUQuota, valued as DirectiveCpuTime { threads } rather than the general percentage arm systemd_directive_value_grain filed as latent — that filing's obstacle ("a fraction of a host figure this repository does not hold") doesn't apply, since CPUQuota is CPU-time-per-wall-second where 100% is one CPU by definition. The memory half of that filing stays open.

The knob rides ConvergeTarget's existing property-generic per-slot mechanism, which per_slot_tasks_max already uses; its own declaration declines the ~96-site rename to PerSlotUnitProperty and binds it to the HostAxisBundle P3 lane.

The ceiling is a function of where compiler work is charged. runner_cpu_boundary_for consumes the compile-pool topology and refuses under CompilePoolSliceDeclared, because compile_pool_bytes_for charges that pool in bytes only and 21 × 6 = 126 of 128 leaves nothing for it. The topology flip cannot land without rederiving width and ceiling together.

RunnerCpuBoundary has exactly one constructor. An earlier revision carried DisjointCpuSet; review 57675 showed it converged successfully while realizing nothing, so it was deleted rather than answered with a refusal — §4b ranks construction above proof. Trigger to reinstate: an AllowedCPUs directive and a producer that assigns disjoint sets.

5. build_tokens stops being nproc

It was stored raw and converged into the jobserver's token count, so compile concurrency written to every host was every hardware thread — 128 — while extdeps.jobserver already carried the installed daemon's formula, cited from its defaults. Calling it yields 120 on srv1 and 62 on srv2; 62 is exactly what that daemon's own header predicts for srv2, which is the corroboration that this is the installer's intent. An absent citation refuses.

Known follow-up, not resolved here: this correction does not repair the acute starvation it sits next to — a correctly sized pool still leaks to zero, because permits are held by killable clients and returned only cooperatively. Per the FLEET-RESOURCE acute ruling this field must not stay a normal RunnerHostDeployment field; it moves behind a bounded LegacyHostJobserverBridge or is deleted with the old root at cutover. Raw nproc under a rung drop is explicitly not an option.

6. CtrlBuild deleted

Along with the build_wrapper carrier: one consumer, linked_cache_interface already expresses the relation, BakedInImage/NoBuildWrapper had none — so removing only the variant would leave a permanently-Absent field where an authority used to be.

Not closed by this PR

  • host_memory_admitted_width is still a per-host literal table (25/5/29/29) — see the layering argument in §1.
  • host_held_runner_width still returns a literal 5 for srv2 by host-name comparison; a declared operator hold awaiting a call on refusal vs a §4b rung drop.
  • Session containers carry CPUWeight, not a CPU ceiling, so host CPU conservation is not closed by per-runner quotas alone.

7. The ratio was read as bytes (fixes the floor red on 22abdb48)

The floor refused five witnesses. Root cause, measured by rendering the deploy manifest rather than inferred: gunbc_runner_memory_per_core returned gibibyte(quoted_numeral(q)) into a declared -> ByteSize. It typechecked, and the value was Measure { count: 3 } — three bytes per core. cores_per_slot became 5,726,623,062, host_cpu_admitted_width divided to zero, and every host reached deployment=refused cause=zero_runner_width. The same instrument now shows srv1/srv3/srv4 at 21 runners, per_slot_cpu_quota=600%, build_tokens=120, with srv2 refusing on its pre-existing session denominator.

The basis now travels with the ratio. sample_quoted_basis folds the sampled rows' basis exactly as sample_unit_label already folds their unit word, and the conversion is the extdeps authority's own quoted_magnitude_bytes, which refuses on a genuinely basis-less sample instead of letting a consumer substitute one. The unstated and mixed arms still degrade to the basis-less magnitude — that is the honest reading when a sample really does not agree.

The refusing arms now refuse. Every non-uniform reading mapped to byte_size(0) and relied on the zero travelling three functions to surface as ZeroRunnerWidth. That is an absorbing arm wearing a refusal's name, and it is precisely why a unit bug presented as a fleet-wide width of zero under a diagnostic naming neither the ratio nor the conversion. RunnerMemoryPerCoreResolution, RunnerCoresPerSlotResolution and CpuAdmittedWidth carry the cause; ci_runner_placement refuses by name on RunnerCoreRatioUnresolved; the CPU axis reports AxisUnmeasured rather than admitting a width it could not compute.

Correcting a claim in an earlier revision of this body. It said "the live path is honest", which is true of one path and not of the wider production surface. gunbc_runner_slots_per_host still returns Int, and it is consumed directly by four RunnerHostDeploy data rows — where it becomes the materialization target, so an unresolved ratio rendered as zero would change the desired runner population from the host's admitted roster to an empty one — and by host_axis_caps, as the upper bound of the runner-unit roster whose system-control shadows get reverted, where a zero produces no reverts and can leave stale effective caps in place. Removal ownership refuses destructive teardown, which contains the harm, but the plan would still be answering the wrong question. The placement path is honest; the width chain is not, and that is what the stacked cascade exists to close.

runner_memory_per_core_of takes the reading as a parameter, for the same reason §3's standing does: a zero-argument derivation over the live census can only be exercised on whatever the survey currently says, so its refusing arms would be permanently unreachable and a check on them would be a decoration. Two REDs (basis-less uniform reading; absent survey) plus a positive control pinning the vendor's 3 GB decimal — which doubles as the regression control, since reading that numeral as gibibytes or failing to convert it both go red there.

Filed, not fixed here: the wall under all of this is inert

std.measure declares type Measure<Q, S, M> { count: M }. Q and S appear in no field — they are phantom, so ByteSize = Measure<Memory, One, Nat> and Gibibyte = Measure<Memory, Gibi, Nat> have identical representations, and the declared-return path admitted the pair.

Narrowing an earlier version of this claim. It said the two "unify freely", which is stronger than what was measured and is false: test.claim.declared_type_inhabitance_direct_call_witness already refuses Wrapper<KindB> at a Wrapper<KindA> parameter with K phantom and the representations identical, so applied-argument identity is enforced on at least one direct-call path. The accurate claim is that it was lost on the declared-return path, and the complete expected-type matrix is not yet measured.

That module's own prose claims the opposite: carrying them as two Measure types at two Scales "makes the comparison a conversion the author must write ... which is the same resolution ByteBasis reaches by validation and this reaches by construction."

Stated at the grain the evidence supports. The universal Measure guarantee is not established across expected-type paths. Two specimens prove two open paths — the declared-return Gibibyte → ByteSize, and a resolution coproduct reaching a one-field measure parameter through a call. At least one direct-call applied-argument path is closed. Other quantity, scale and clock-basis applications are at risk but unmeasured until the expected-type matrix runs. The class-wide rung is outside the ladder, because §4b takes the minimum over in-scope paths and the minimum is silent — but "every distinction is structurally open" is more than has been measured, and an earlier revision of this section said it. Whether the repair is a checker change or a modeling one (scale as a field, or distinct nominal types) decides the shape of the fix, so it is filed as its own lane rather than widened into this PR.

…vention read from the survey, and CtrlBuild deleted

The committed runner width had TWO producers and they disagree. gunbc.runner_slot_allocation
gunbc_runner_slots_per_host is the minimum over the memory, CPU and disk axes; gunbc.fleet_host_budget
declared_runner_count_for was the memory axis alone, and RunnerHostDeployment.runner_count consumed
the second one. Memory bound every host in this fleet's history until the 2026-08-22 upgrade and now
binds only srv2, so the memory-only producer no longer states the commitment anywhere it is asked --
it reports 25/29 where the fleet commits 21. The witness at test.claim.runner_slot_provision already
DOCUMENTED the min as the derivation while production took the other route, which is the fork stated
in its own comment. declared_runner_count_for is deleted and its three production consumers
(ci_runner_placement, runner_slot_provision, host_converge_slice1) ask the single producer.

THE 1 CORE : 3 GiB RATIO IS SURVEYED, NOT AUTHORED. gunbc_runner_memory_per_core was a
byte_size(3000000000) literal restating one provider's published figure as gunbc authority -- a second
name for a number extdeps already cites, free to drift from the catalog it was copied from. It now
reads through gunbc.runner_shape_census memory_per_vcpu against the Ubicloud aarch64 catalog.

GitHub Actions is deliberately NOT a second source for the ratio, and the census says why rather than
this commit asserting it: GHA's aarch64 catalog reads 4 per vCPU where uniform and carries two sizes
that are not integral at all (arm-5core at 14, arm-64core at 208), so memory_per_vcpu REFUSES on it.
A provider that aims at a ratio has not published one, and averaging the survey would mint a figure no
vendor states.

The BASIS is a gunbc adoption because the census refuses to guess it: QuotedByteMagnitude is
QuotedWithoutBasis, no surveyed vendor page saying whether its "GB" is decimal or binary. The fleet
reads the convention as gibibytes (operator ruling 2026-08-30). The deleted literal read it as decimal;
both yield 6 cores per slot at the current 16 GiB anchor, and they diverge at other anchors -- the
anchor being declared temporary is exactly why the basis is recorded instead of surviving as an
accident of today's number. An ungrounded reading yields zero bytes per core, which drives width to
zero and reaches the existing ZeroRunnerWidth refusal: it admits no runners rather than every core.

CtrlBuild is deleted rather than renamed, and the build_wrapper carrier goes with it. It had exactly
one consumer, linked_cache_interface already expresses the relation, and BakedInImage/NoBuildWrapper
had none -- so removing only the variant would leave a permanently-Absent field standing where an
authority used to be. A deprecated project name is not a kind of compiler wrapper.

test.claim.runner_placement's per-host row also stops asserting f(x) == f(x): it now joins the
committed count to each axis it must not exceed, which a deploy row raised past its memory or CPU
admission falsifies.

NOT YET CLOSED, and named so the next change is not mistaken for a fresh discovery:
host_memory_admitted_width is still a per-host literal table (25/5/29/29) rather than a derivation
from usable RAM minus overhead and session slice; host_held_runner_width still returns a literal 5 for
srv2 by host-name comparison; and RunnerHostDeployment still carries no per-slot CPU entitlement, so
cores_per_slot decides how many units exist and nothing about the units after creation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
@gunbai-bot

gunbai-bot Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Correction to this PR's stated justification

The change stands; the reason given for it is wrong, and I'd rather correct it than let it merge on a false premise (including past an approval that cited it).

What the PR body claims: that declared_runner_count_for and gunbc_runner_slots_per_host are two producers that disagree — "21 committed against a memory admission of 25/29".

What is actually true: they were identical by construction.

gunbc_runner_pool_budget_for(host) = gunbc_runner_slice_cap_declared(host)
                                   = gunbc_runner_slots_per_host(host) × gunbc_runner_slot_desired().memory_max

gunbc_ci_runner_cgroup_memory_cap  = gunbc_runner_slot_desired().memory_max      (ci_floor_measurement.dag:18)

declared_runner_count_for(host)    = floor(pool_budget ÷ gunbc_ci_runner_cgroup_memory_cap)
                                   = floor(slots × memory_max ÷ memory_max)
                                   = gunbc_runner_slots_per_host(host)

It multiplied by a constant and divided by the same constant to return its own input. The 25/29 figures belong to host_memory_admitted_width, a different function; I attached them to the wrong symbol.

So the deletion is right for a stronger reason. Not a competing authority — a §5 tautology, the measure() == measure() shape: a second name whose entire content was a round trip. It also means the change is behaviour-preserving for runner_count, which is the correct expectation to review against rather than the numeric shift the body implies.

The underlying defect is §3 nicknaming, and it is worth naming here because it is what the next PR in this lane has to repair: gunbc_runner_pool_budget_for is named as a supply fact — what a host makes available to runners — while its body computes demand, what the committed slots sum to. The genuine supply-side function lives in the same module (fleet_host_budget.dag:358 runner_slice_cap(host, overhead, session_slice, headroom, compile_pool)) and is wired at :435; the nickname shadows it wherever "budget" is asked for.

That nickname is also why host_memory_admitted_width is an authored literal table (25/5/29/29) rather than a derivation: the memory axis has no supply-side denominator wired to it. The literal is what keeps the graph acyclic — deriving the axis from the pool budget would close the loop, since the budget is itself derived from the width. Repairing it in place is therefore impossible without a cycle; the derivation's inputs live above runner_slot_allocation, so it has to be hoisted into one host-keyed allocation value. That is FLEET-RESOURCE-0A's FleetHostResourceAllocation, and this is the argument for it from the tree rather than from the design note.

No code change accompanies this comment — the diff is unchanged and I am not re-litigating the approval, only the sentence it was granted against.

— sent from eager-ram-258

Brian Searls and others added 3 commits August 30, 2026 21:03
…compile-token budget stops being nproc

cores_per_slot decided how many runner units to create and then vanished. srv1 and srv2 carry
MemoryMax as their ONLY resource line -- CPUWeight [not set], CPUQuota infinity, AllowedCPUs empty --
so the width was CPU-derived while the units it created were told nothing about CPU at all. Admission
without confinement. The CPUWeight=100 that systemd_unit_file_witness_test expects is not on either
host, and would not be a ceiling if it were: CPUWeight is a RELATIVE share against siblings and
guarantees no amount of anything, which is a different contract from a maximum.

extdeps.systemd gains CPUQuota, and the value arrives as DirectiveCpuTime { threads } rather than as
the general percentage arm gunbc.systemd_directive_value_grain filed as latent. That filing named its
own obstacle -- a percentage is "a fraction of a host figure this repository does not hold" -- and the
obstacle does not apply here: CPUQuota is denominated in CPU time per wall second, where 100% is one
CPU by definition rather than a share of the machine, so the quantity is a multiple of a unit already
carried as HardwareThreadCount. A general percentage arm would also admit MemoryMax=50%, which IS a
fraction of a figure nothing here holds; the memory half of that filing stays open with its trigger.

The knob rides ConvergeTarget's existing per-slot mechanism rather than forking a variant. That target
is already property-generic and per_slot_tasks_max already rides it with a pids count; its own
declaration declines the ~96-site rename to PerSlotUnitProperty/value_raw and binds it to the
HostAxisBundle P3 lane. This is the third value to arrive at a field still named bytes_value, which
strengthens that lane's case rather than pre-empting it.

THE CEILING IS A FUNCTION OF WHERE COMPILER WORK IS CHARGED, so the boundary consumes the compile-pool
topology instead of cores_per_slot alone. Under CompilePoolInRunnerSlots a job's compiler children run
inside its own runner boundary and the host sum is width x cores_per_slot + reserve. Under
CompilePoolSliceDeclared they are charged to a separate pool and the sum gains a third term -- and
compile_pool_bytes_for charges that pool in BYTES, with no CPU charge for it anywhere in the tree. On
this fleet the gap is not a margin: 21 slots at 6 threads is 126 of 128, leaving two threads for a pool
that would carry every cache-miss rustc on the host. So the declared-slice arm REFUSES
(HostCpuAllocationUnchargeable) rather than emitting a quota it cannot justify, which makes the
topology flip impossible to land without rederiving width and ceiling together. Next-rung trigger: a
compile-pool CPU charge in the host allocation.

build_tokens STOPS BEING nproc. It was stored raw and converged straight into the jobserver's token
count, so the compile concurrency written to every host was every hardware thread on the machine --
128 -- while extdeps.jobserver already carried the installed daemon's own formula, cited term for term
from its defaults: min(cores - margin, floor((MemTotal - reserve) / mib_per_job)). Two producers for
one number is the defect rather than the size of either: the compile pool could be sized from the
derivation while the host was converged to nproc, and nothing joined them. Calling the derivation
yields 120 on srv1 and 62 on srv2 -- and 62 is exactly the figure that daemon's own header predicts
for srv2, which is the corroboration that this is the installer's intent and not a new policy. srv1
differs from the header's 44 only because its RAM is now 502 GiB rather than the 93 GiB that header
was written against. An absent citation refuses (JobserverCitationAbsent) rather than falling back.

DisjointCpuSet is modeled and produces no knob. Nothing constructs it and extdeps.systemd carries no
AllowedCPUs= to render it with, so the arm states that by emitting nothing rather than degrading to
the quota it did not promise. It exists so the contract this fleet does NOT make -- pairwise-disjoint
per-slot CPU sets -- has a name. Trigger: an AllowedCPUs directive plus a producer that assigns sets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…he zero-slot-shape refusal the deleted round trip was carrying by accident

TWO FIXES, ONE OF WHICH IS A REAL HOLE IN THE PRECEDING COMMIT.

FIRST, the parse refusal. The DisjointCpuSet rationale sat inside runner_cpu_boundary_knobs's match
body, and DESIGN 4c admits only standalone leading blocks at module-item grain -- trailing, body and
unattached forms refuse until separately modeled. Six located errors, correctly. The block moves above
the declaration it describes; nothing about the arm changes.

SECOND, and this is the substantive one: deleting declared_runner_count_for removed a refusal it was
carrying by accident, and the deletion's stated justification was too strong.

measure_fit_count_floor answers ZERO when its divisor is zero rather than dividing, so
fit(slots * memory_max, memory_max) collapses to slots for every memory_max > 0 and to ZERO at zero.
The deletion was therefore behaviour-preserving on the current tree and under that side condition --
not definitionally identical over every tree the source can express. Calling it an unconditional
tautology overstated it in the same direction the PR body's original "two producers disagree" claim
did: a plausible reading that survived because nobody followed the projection into its own zero arm.

The derived path would still refuse without help, because cores_per_slot ceilings memory_max against
the surveyed memory-per-vCPU convention and host_cpu_admitted_width returns zero rather than dividing
by a zero per-slot count. THE HELD PATH WOULD NOT. gunbc_runner_slots_per_host consults
host_held_runner_width FIRST, and that arm answers with an authored number that never consults the
slot shape -- so srv2's held five would survive a shape whose memory ceiling admits nothing. That is
the single place the accidental refusal was load-bearing, which is why the standing is checked BEFORE
the held arm rather than folded into the derivation.

RunnerSlotShapeStanding states it as a construction: a zero memory_max admits no runners, the width is
zero, and zero reaches the existing ZeroRunnerWidth refusal so the host refuses rather than deploys.

THE RED IS AUTHORABLE, WHICH IS WHY THE CHECK IS WORTH WRITING. gunbc_runner_slot_desired() is a fixed
declaration, so no witness can make the live fleet carry a zero ceiling; the standing takes the shape
as a parameter, and that is exactly what lets a fixture express a state the accepted corpus cannot.
w_zero_memory_slot_shape_admits_no_runners authors memory_max = 0 and establishes the refusal;
w_live_slot_shape_is_admitted is the positive control that keeps the first from being unconditional.
Without the parameter the check would have been permanently green by construction and cited as
coverage while proving nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…(review 57675)

REQUEST_CHANGES was right and the annotation defending the arm was wrong. runner_cpu_boundary_knobs
answered DisjointCpuSet with an EMPTY knob list, so a deployment carrying that boundary converged
SUCCESSFULLY while the host was told no CPU ceiling at all. That is a fail-open path (DESIGN 5): the
line does not stop, nothing is typed or located, and the manifest's "unemitted" label is a string in
a receipt rather than a refusal on the operational path. My comment claimed emitting nothing was the
honest arm; emitting nothing is only honest if something refuses, and nothing did.

Both offered remedies close it, and DESIGN 4b ranks them: construction over proof. Propagating a typed
refusal through convergence would leave the invalid state writable and make safety depend on that
refusal executing -- mechanically preventable. Deleting the constructor means no Accepted program can
carry a boundary this emitter cannot realize -- structurally impossible, and a smaller change. So the
variant goes.

RunnerCpuBoundary now has exactly one constructor, which makes runner_cpu_boundary_knobs and
runner_cpu_boundary_text total without an arm that realizes nothing. Every representable boundary
emits a knob.

The distinction the variant recorded survives as an annotation, which is where a promise nobody can
construct belongs, together with the trigger for reinstating it: an AllowedCPUs directive in
extdeps.systemd AND a producer that assigns disjoint sets. Both, because either alone reproduces
exactly the unrealizable state this deletion removes.

Also corrects the sentence above it, which still said DisjointCpuSet "is modeled rather than omitted"
-- a note describing a deleted authority in the present tense is premise contamination whichever
direction it points, and this lane has already flagged one of those in gunbc.fleet_intent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
@gunbai-bot

gunbai-bot Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

review 57675 (REQUEST_CHANGES) — fixed in 22abdb4882

The finding is correct and the annotation defending the arm was wrong. runner_cpu_boundary_knobs answered DisjointCpuSet with an empty knob list, so a deployment carrying that boundary converged successfully while the host was told no CPU ceiling at all. That is fail-open under §5 — the line doesn't stop, nothing is typed or located — and the "unemitted" string in the manifest is a receipt label, not a refusal on the operational path. My comment claimed emitting nothing was the honest arm; emitting nothing is only honest if something refuses, and nothing did.

I took the deletion rather than the refusal, of the two remedies you offered. §4b ranks them: construction over proof. Propagating a typed refusal through convergence leaves the invalid state writable and makes safety depend on that refusal executing — mechanically preventable. Deleting the constructor means no Accepted program can carry a boundary this emitter cannot realize — structurally impossible, and the smaller change.

RunnerCpuBoundary now has exactly one constructor, so runner_cpu_boundary_knobs and runner_cpu_boundary_text are total with no arm that realizes nothing. Every representable boundary emits a knob.

The distinction the variant recorded survives as an annotation — which is where a promise nobody can construct belongs — with the trigger for reinstating it stated as a conjunction: an AllowedCPUs= directive in extdeps.systemd and a producer that assigns disjoint sets. Both, because either alone reproduces exactly the unrealizable state this deletion removes.

Same commit also repairs the sentence above the type, which still read that DisjointCpuSet "is modeled rather than omitted" — a note describing a deleted authority in the present tense, which is the contamination this lane already flagged once in gunbc.fleet_intent.

— sent from eager-ram-258

Brian Searls and others added 3 commits August 30, 2026 23:26
…t, and the refusing arms refuse

The floor went red on five witnesses because gunbc_runner_memory_per_core returned
gibibyte(quoted_numeral(q)) into a declared -> ByteSize. It typechecked, and the value
was Measure { count: 3 } -- three BYTES per core. cores_per_slot became 5,726,623,062,
host_cpu_admitted_width divided to zero, and every host in the fleet reached
deployment=refused cause=zero_runner_width. Measured by rendering the deploy manifest,
not inferred; the same instrument shows srv1/srv3/srv4 back at 21 runners with
per_slot_cpu_quota=600% and build_tokens=120.

Two defects, one of them mine twice over.

THE BASIS WAS DISCARDED AND THEN GUESSED. extdeps.cloud.ubicloud quotes its catalog as
QuotedDecimalGigabytes, so the basis was stated all along -- but per_vcpu_magnitude
rebuilt every ratio as QuotedWithoutBasis, so the census threw away the answer its own
rows carried and each consumer had to re-guess it. One did, and guessed gibibytes. The
annotation defending that guess cited an operator ruling that does not exist; it is
deleted. sample_quoted_basis now folds the sampled rows' basis the same way
sample_unit_label already folds their unit word, and the conversion is the extdeps
authority's own quoted_magnitude_bytes, which refuses on a genuinely basis-less sample
rather than letting a consumer substitute a basis of its own.

THE REFUSING ARMS ANSWERED ZERO. Every non-uniform reading mapped to byte_size(0) and
relied on the zero travelling three functions to surface as ZeroRunnerWidth. That is an
absorbing arm wearing a refusal's name: it is why a unit bug presented as a fleet-wide
width of zero with a diagnostic naming neither the ratio nor the conversion.
RunnerMemoryPerCoreResolution, RunnerCoresPerSlotResolution and CpuAdmittedWidth carry
the cause instead, and gunbc.ci_runner_placement refuses by name on
RunnerCoreRatioUnresolved. The CPU axis reports AxisUnmeasured rather than admitting
a width it could not compute.

runner_memory_per_core_of takes the reading as a parameter so the refusal has an
authorable RED: a zero-argument derivation over the live census can only ever be
exercised on what the survey currently says, so its refusing arms would be permanently
unreachable and a check on them would be a decoration. Two REDs (basis-less uniform
reading, absent survey) plus a positive control pinning the vendor's 3 GB decimal --
which is also the regression control for this exact bug, since reading that numeral as
gibibytes or failing to convert it both go red there.

NOT fixed here, and larger than this PR: std.measure declares Measure<Q, S, M> with Q
and S appearing in no field. They are phantom, so ByteSize and Gibibyte are structurally
identical and unify freely -- which is why the wrong-scale return typechecked. That
module's own prose claims two Measure types at two Scales make the conversion one "the
author must write ... by construction". They do not. Filed for its own lane rather than
widened into this one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…ted cores_per_slot had no reader

Follow-up to the ratio repair, from review of the change itself.

THE WALL WAS COMPARING NONSENSE. gunbc_runner_slot_allocation_wall_holds still passed
gunbc_runner_cores_per_slot() to hardware_thread_count_value and gunbc_runner_memory_per_core()
to byte_size_count after both had become resolution coproducts. It typechecked, and the
required floor stayed green over 3,136 witnesses -- a two-variant coproduct reached a
parameter declared as a one-field measure, at a call-argument position. The relation now
matches both resolutions and refuses on either unresolved arm.

That admission is a floor-level finding and it is recorded, NOT claimed as enrolled.
test.claim.declared_type_inhabitance_direct_call_witness already refuses distinct unapplied
constructors passed through a call, so this is not the general call-argument case; the
discriminator is unmeasured, and the candidates are a coproduct producer, an alias-expanded
generic application at the parameter, and cross-module resolution. Naming one without the
matrix would repeat the guess that caused the unit bug. The trigger is the matrix itself.

HostRunnerSlotAllocation.cores_per_slot is deleted. The module's own note already called it
"a number carried to consumers and constraining nothing"; with CPU a real admission axis the
figure constrains through host_cpu_admitted_width, and nothing read the reported copy.

The canonical width API still collapses unresolved to 0 and that is now a DECLARED gap with a
capability trigger rather than a silent one. ci_runner_placement no longer depends on it --
it consults the ratio directly and refuses with RunnerCoreRatioUnresolved carrying the cause
-- so the live path is honest. Closing it fully means RunnerWidthResolution reaching sixteen
production call sites across nine modules, several of them `data` rows with no arm to match
on. Landing half of that cascade would mint sixteen new silent defaults, which is the defect
this branch exists to remove, so it is its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…7696)

review 57696 asks that sample_quoted_basis_eq -- a hand-rolled Boolean over a coproduct --
be routed through a canonical surface or carry a bounded disposition.

Two halves, answered differently.

THE GENERAL CLAIM DOES NOT HOLD, and it is worth stating rather than quietly complying: there
is no predicate-dissolution rule in DESIGN.md or docs/design-ledgers.md, and the idiom is the
corpus norm in the most load-bearing layer there is. std.effect_grant verb_eq and
std.induction recursion_shape_eq are nested exhaustive matches over nullary coproducts
returning true/false -- character for character the shape objected to. If that is substrate
debt it is std's, fleet-wide, and not something this PR introduced.

THE LOCAL POINT IS RIGHT ANYWAY, by a better route than the one suggested. The predicate
existed only to fold basis unanimity, and an accumulator-versus-element comparison carries an
asymmetry it then has to describe: the first row is privileged, and the predicate must invent
a meaning for "not yet seen" -- which is why SampleBasisAccum needed a `seen` flag beside the
value. Counting each arm removes the predicate, the accumulator type and the flag together,
and unanimity becomes a statement about the tallies, decided identically whichever order the
rows arrive in. Three declarations become two, and the order-independence is now structural
rather than argued.

The refusal behaviour is unchanged: a sample with any unstated row, or with both bases
present, still degrades to the basis-less magnitude that quoted_magnitude_bytes refuses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
@gunbai-bot

gunbai-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in the next push, and the general claim checked rather than assumed.

The local point is right, and I took a better route than the one suggested. sample_quoted_basis_eq is gone, along with SampleBasisAccum and sample_quoted_basis_step. The predicate existed only to fold basis unanimity, and an accumulator-versus-element comparison carries an asymmetry it then has to describe: the first row is privileged, so the accumulator needed a seen flag beside the value purely to give "not yet seen" a meaning. Counting each arm removes the predicate, the accumulator type and the flag together — three declarations become two, and order-independence becomes structural instead of argued. Refusal behaviour is unchanged: any unstated row, or both bases present, still degrades to the basis-less magnitude that quoted_magnitude_bytes refuses.

The general claim does not hold, and I would rather say so than comply silently. There is no predicate-dissolution rule in DESIGN.md or docs/design-ledgers.md — I checked both. And the idiom is the corpus norm in the most load-bearing layer there is: std.effect_grant verb_eq and std.induction recursion_shape_eq are nested exhaustive matches over nullary coproducts returning true/false, character for character the shape objected to. If that is blocking substrate debt, it is std's and fleet-wide; it is not something this PR introduced, and a bounded disposition naming this one function as the debt would have been a false record.

Worth flagging for the same reviewer's attention, since it is the substantive type-system defect on this branch and outranks the predicate: gunbc_runner_slot_allocation_wall_holds was passing two coproducts into parameters declared as one-field measures (hardware_thread_count_value, byte_size_count). It typechecked, and the required floor stayed green over 3,136 witnesses. That is repaired in f2eda971, and the specimen is recorded — explicitly not claimed as enrolled — because test.claim.declared_type_inhabitance_direct_call_witness already refuses distinct unapplied constructors through a call, so this is not the general call-argument case and the discriminator is unmeasured.

— sent from eager-ram-258

Brian Searls and others added 3 commits August 31, 2026 00:45
review 57699 (non-blocking) notes slice_cpu_quota has no consumers: the live CPU-quota
producer is gunbc.host_converge runner_cpu_boundary_knobs, which reaches the property-generic
PerSlotMemoryCap target directly. It read as set-completion beside slice_cpu_weight, whose
sibling IS consumed.

Dropped rather than noted. DESIGN 6 names a new artifact with no final consumer as
experimental residue, and the reviewer's own alternative was to drop it; a note would have
left the residue and added prose. It costs one line to reinstate when a slice-directive
consumer exists, and the deletion orphaned the CPUQuota import here, which goes with it --
the property constructor is still consumed by extdeps.systemd systemd_cpu_quota_property.

The reasoning MOVES rather than dying with the function. Why the arm is DirectiveCpuTime and
not the general DirectivePercentage the systemd_directive_value_grain filing anticipated --
CPU time per wall second is a multiple of a unit this repo carries, where MemoryMax=50% is a
fraction of a host figure it does not -- is a fact about the VALUE ARM, not about a mint, so
it now sits on SystemdDirectiveValue where it is load-bearing.

The review's second observation is accurate and needs no change: the bytes_value field
carrying "600%" is already declared at gunbc.host_converge runner_cpu_boundary_knobs, which
names PerSlotMemoryCap's own declination of the ~96-site rename and the HostAxisBundle P3
lane that owns it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…and correct two overclaims

CONDITION 5, AND I WAS WRONG TO DEFER IT. I had argued the compiler admission could only be
recorded, not enrolled, because the discriminator between candidate causes is unmeasured and
asserting one would repeat the guess that caused the unit bug. The premise was right and the
conclusion did not follow: a fixture need only say THIS SOURCE CONTAINS A TYPE
NON-INHABITANCE, SO COMPILATION MUST PRODUCE A BLOCKING DIAGNOSTIC. It never has to say why.
Root-cause uncertainty is a reason to keep the fixture mechanism-neutral, not to omit it --
and once the production specimens are repaired, an unenrolled class loses its executable
subject entirely.

Two pairs, both minimised from shapes found in production on this branch rather than
constructed to prove a point:

  a Gibibyte returned from a function declared -> ByteSize -- the incident's own shape, which
  typechecked and yielded Measure { count: 3 }, three BYTES per core;

  a two-variant resolution coproduct at a parameter declared as a one-field measure, from this
  module's own aggregate wall, minimised to no aliases, no generics and one import.

Each carries a positive control differing only in the declared type, so an arm that refused
every Measure return or every coproduct would satisfy the red while destroying the corpus.
Whichever way the synthetic coproduct arm lands is a result: if it refuses while the
production shape admits, the matrix starts one candidate shorter.

TWO OVERCLAIMS CORRECTED, both mine and both the same failure of grain.

"Every quantity/scale distinction built on Measure is structurally open" outran the evidence.
Two paths are proven open; at least one direct-call applied-argument path is proven CLOSED by
the existing sibling fixture; the rest are unmeasured. The class-wide rung stays outside the
ladder because 4b takes the minimum over in-scope paths, which is a different claim.

"The live refusal path is honest" was true of one path and not of the production surface.
gunbc.runner_host_deploy takes the Int directly into four deploy rows where it becomes the
materialization target, so an unresolved ratio read as zero would swap the host's admitted
roster for an empty one; gunbc.host_axis_caps uses it as the upper bound of the roster whose
system-control shadows are reverted, where zero produces no reverts and can leave stale
effective caps standing. The gap is worse than the earlier note described, which is why the
cascade is mandatory rather than optional.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…ng narrowed

The floor at 5c3a31c measured the four new fixtures. One is red, three are green, and the
green ones are why this enrols a single identity rather than a class:

  w_a_gibibyte_returned_at_a_declared_bytesize_is_refused   FAILED  <- enrolled here
  w_a_gibibyte_returned_at_a_declared_gibibyte_is_admitted  passed  (positive control)
  w_a_coproduct_at_a_record_parameter_is_refused            passed  (refused, as it should be)
  w_a_matched_record_at_a_record_parameter_is_admitted      passed  (positive control)

THE GREEN COPRODUCT ARM IS THE RESULT, not a formality. A minimal two-variant coproduct
reaching a one-field record parameter IS refused, while the production specimen -- a
resolution coproduct reaching a std.measure alias parameter across a module boundary -- was
admitted over 3,136 green witnesses. A coproduct producer is therefore NOT sufficient to
explain that admission, and the matrix starts one candidate shorter than it would have.
Both open specimens go through std.measure aliases; both closed ones do not. That is a
direction, and the roster row says so while explicitly declining to assert a cause -- naming
one on this evidence would be the guess that produced the unit bug in the first place.

CAUSATION STATED SO A READER CAN CHECK IT RATHER THAN TRUST IT: this branch changes no
compiler source at all -- its entire diff against b60fc7d under src/ was empty before this
roster edit -- so no change on it can have opened a type judgment. The admission predates the
fixture, and no control build against main is needed to establish that.

Dissolve-on is the capability, not an artifact: the expected-type matrix locating the
judgment that drops applied-type arguments, and the repair landing. The roster's
self-emptying arm makes that announce itself -- when the wall arrives this row passes, reds
the build, and names itself for removal. The witness is permanent under 4b(4) and flips to a
regression control rather than retiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
@gunbai-bot

gunbai-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

The matrix ran, and the cause is located. chunk_24's annotation deliberately declines to name a mechanism and lists the candidates as coproduct producer, transparent alias expansion, generic application identity, cross-module resolution, or an interaction. Measured on synthetic source with no std.measure, no alias, no coproduct and no module boundary, it is generic application identity — specifically the type ARGUMENT, and only the argument.

Run with a locally built gunbc against compile_dag_diagnostic_census, counting diagnostics of any class (not just DeclaredTypeNotInhabited, which matters — see the instrument note below):

source shape diagnostics verdict
-> Int { "x" } — literal, non-generic 1 refused
-> Int { produces_string() } — call result, non-generic 1 refused
-> Box<TagA> { 5 } — wrong constructor head 1 refused
-> Box<TagA> { produces_a() } — matching argument (positive control) 0 correctly admitted
-> Box<TagB> { produces_a() } — phantom parameter, wrong argument 0 admitted
-> Box<TagB> { produces_a() } — parameter used in a field, wrong argument 0 admitted

where type Box<T> = MkBox { n: Int } (phantom) or MkBox { v: T } (used), and TagA/TagB are distinct empty records.

So declared-return inhabitance is checked for non-generic types — in literal and call-result position alike — and is checked for the generic type constructor. It is not checked for the type argument. Whether the parameter is phantom or carried in a field makes no difference, which kills my own starting hypothesis.

That is exactly the incident's shape: Gibibyte and ByteSize are both applications of Measure differing only in type arguments, so fn gunbc_runner_memory_per_core() -> ByteSize { gibibyte(...) } had nothing left to check. It also explains why the sibling direct-call witness already refuses distinct unapplied constructors — that path compares heads, which is checked.

Two candidates are falsified, not merely unselected. It is not the coproduct producer and not cross-module resolution: neither appears in any fixture above, and the defect reproduces without them. Alias expansion is likewise unnecessary. The minimal reproduction is six lines of source with one type parameter.

An instrument note that changes how these fixtures should be read. violation_count(..., wanted: "DeclaredTypeNotInhabited") returned 0 for a blatant -> Int { "not an int" }, while the total-diagnostic count for that same source was 1. The compiler refuses it under a different class name. So a > 0 assertion on that specific class can read as "admitted" when the program was in fact refused, and the four fixtures in chunk_24 inherit that hazard. Counting all classes, plus a positive control that must be non-zero, is what made this matrix trustworthy — without the blatant control I would have reported "phantom parameters are ignored" from three zeros that were measuring nothing.

I have not modified this PR. The fixtures are held for enrollment as a follow-up so this stays merge-ready.

— sent from eager-ram-258

Brian Searls and others added 3 commits August 31, 2026 12:28
…ocated cause

REVIEW OBJECTION, ACCEPTED AND THEN FOUND TO BE LARGER THAN STATED. The four enrolled
fixtures used violation_count(wanted: "DeclaredTypeNotInhabited") as an ADMISSION oracle. That
distinguishes one diagnostic spelling from its absence, not refusal from admission -- so a
future repair refusing these specimens under a more precise class would leave the enrolled red
held forever while the hole it names was already shut.

Fixing the oracle exposed something worse. Counting every census row made a VALID source score
non-zero, because the census reports advisories as data beside refusals and says in its own
voice that callers filter; the count now goes through census_blocking_rows, which is the
severity authority rather than a second opinion on it. And with severity honest, the positive
control for the Measure pair FAILED. The reason is that the census compiles a synthetic
SINGLE-MODULE source, so corpus imports do not resolve: the trivially valid program
  module p / import std.measure { Gibibyte, gibibyte } / fn probe() -> Gibibyte { gibibyte(count: 3) }
produces 92 BLOCKING diagnostics. Both the old red and its control were drowning in
UnresolvedType and the class filter hid all 92 -- the only reason that pair ever appeared to
work. The old red refused because its imports did not resolve, and its control refused for the
identical reason while asserting admission. Neither ever measured the type mismatch.

So the Measure pair is REPLACED by import-free specimens that express the same shape with types
the probe declares itself: Box<TagA> and Box<TagB> stand exactly where Gibibyte and ByteSize
stood, two applications of one constructor differing only in a type argument.

WITH THAT, THE MATRIX LOCATES THE CAUSE instead of pointing at a direction. Measured, all ten
arms executed:

  wrong type argument at a declared return        ADMITTED  <- enrolled red
  wrong type argument carried in a field          ADMITTED  <- enrolled red
  matching type argument                          admitted  (positive control)
  wrong constructor head                          refused   (the head IS compared)
  non-generic call result mismatch                refused   (call results ARE compared)
  blatant non-generic mismatch                    refused   (liveness)
  valid source                                    admitted  (opposite control)
  coproduct at a record parameter                 refused
  matched record at a record parameter            admitted  (positive control)
  coproduct refusal classified                    refused   (diagnostic QUALITY, kept separate)

In the declared-return expected-type path, a generic application's CONSTRUCTOR HEAD is compared
but its TYPE-ARGUMENT VECTOR is not. Field occupancy, aliases, coproducts and module boundaries
are each absent from a specimen that still reproduces, so none is a necessary cause -- and the
phantom account I went in believing is dead, since a parameter carried in a FIELD is dropped
just the same.

TWO enrolled identities, not one, because those two specimens fail independently: a repair that
merely noticed arguments occurring in fields would close one and leave the other, and a single
row could not tell them apart.

The dissolution trigger is rewritten for the same reason. It said "the matrix locating the
judgment, and the repair landing" -- and the matrix half is now done while both rows stay red,
which is exactly the grain mismatch DESIGN 4b(3) warns about: a trigger satisfiable while the
capability stays dead. It now names the capability alone, the comparison deciding identity on
head, arity and every type argument recursively.

The claim stays bounded to one path. This measures the declared-return comparison; the
direct-call path may still enforce argument identity, and citing this path as though it settled
the others would be the rung inflation 4b(1) names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…ed twice

An approving review quoted this annotation as naming "the sixteen production call sites". A
census I took independently while planning the RunnerWidthResolution cascade said twelve. The
call-site read says 22 -- every non-comment, non-import occurrence of
gunbc_runner_slots_per_host( under dag/gunbc and src/v2, less the declaration itself. My census
grep silently missed runner_slot_allocation's own internal uses, host_converge_slice1,
ci_runner_placement and runner_slot_provision.

So both numbers were wrong, in opposite directions, and neither was checkable from where it was
written. That is precisely what DESIGN section 6 forbids -- name the instrument, never
transcribe its output -- for the same reason section 3 cites a symbol rather than a line: a
copied number is unreachable from the thing that owns it, so it rots without anyone touching
either end. Here the number was load-bearing, because it sizes the cascade this declared gap
names as its own dissolution trigger.

The fix is not a better number. The annotation now names the PRODUCER and states the count
nowhere, so a reader who wants it runs the read. The one place a figure survives is my own
planning note in scratch, which now says to re-run rather than to trust it.

Nothing about the gap's substance changes: the trigger remains the capability, and the arm still
dissolves when every production call site carries or refuses a cause instead of reading an Int.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
…ecimens and close the no-verdict hole

Four review blockers, and answering the first two made the finding LARGER than the row this
branch was carrying.

WHAT IS ACTUALLY TRUE, measured, all executed on this branch's own binary:

  plain record vs plain record, different fields          ADMITTED
  argument zero, phantom parameter                        ADMITTED
  argument zero, carried in a field (Int vs String)       ADMITTED
  argument one, of three                                  ADMITTED
  argument two, of three                                  ADMITTED
  nested argument at position one                         ADMITTED
  distinct heads, same arity, same argument vector        ADMITTED
  matching applications, each form                        admitted  (positive controls)
  a PRIMITIVE where an application is declared            refused
  a primitive call-result mismatch                        refused
  a blatant primitive mismatch                            refused   (liveness)
  a valid source                                          admitted  (opposite control)

So in declared-return position two NOMINAL types are not compared at all, and refusal survives
only where a PRIMITIVE is on one side. Every generic row is an instance of that, which is why
the plain-record row is enrolled in its own right.

I HAD CLAIMED THE CONSTRUCTOR HEAD WAS COMPARED and narrowed the defect to the type-argument
vector on that basis. The only evidence was a primitive returned where an application was
declared, which establishes nothing about comparing two applications. Box<TagA> and Crate<TagA>
are admitted. That was a guess wearing a measurement's clothes -- the same failure this module
already records against the std.measure pair one paragraph away.

BLOCKER 1, field occupancy. The old specimen used two EMPTY records, which a
representation-oriented comparison flattens identically, so it could not distinguish a discarded
argument from one substituted and compared as identical representation. It is now Box<Int>
against Box<String>, which cannot be flattened together, plus a positive control for the
USED-parameter form specifically -- the phantom form's control does not cover it.

BLOCKER 2, position coverage. Both old reds differed at argument ZERO while the production
incident differs at the MIDDLE of three. A partial repair would have greened them, SELF-EMPTIED
them by this roster's own rule, and left the Gibibyte-to-ByteSize hole open. Positions zero, one
and two now each carry a row, with nesting at position one so "recursively" is checkable.

ARITY IS DELIBERATELY NOT ENROLLED, and not for the reason I was given. The prediction was that
an arity-mismatch source would refuse during generic application formation before reaching the
defective comparison; measured, it is ADMITTED with zero blocking diagnostics. The right reason
is structural: a same-head arity mismatch is UNWRITABLE, since a constructor's arity is fixed by
its declaration, so every arity specimen also changes the head and isolates nothing the head row
already carries. The trigger is narrowed to same-head, same-arity rather than keeping a word no
specimen could discriminate.

BLOCKER 3, the no-verdict hole. refusing_count maps CensusNotRunnable to -1 and every red
asserts > 0, so a source-specific census failure produced exactly the false the roster holds as
AGREEMENT -- the row would sit green-in-the-roster forever while its subject went unmeasured.
The liveness and valid-source controls cannot catch it because they compile DIFFERENT sources.
Every enrolled source now carries a census_ran companion.

BLOCKER 4, the trigger. It promised head, arity and every argument recursively while the matrix
had no arity or nested discriminator. It now names the positions and shapes actually enrolled.

A NEAR MISS WORTH RECORDING. These specimens were first measured with a claim_executor built on
the containment-A branch after merging main, then run against this branch's unmerged corpus. It
surfaced as a floor refusal citing function-equality errors in src/v2 files this branch never
touched -- main's newer wall firing on older rows. Rebuilt on this branch, every result was
identical, so the specimens stand; but the roster was one step from resting on a compiler that
is not the one CI runs.

Local floor: FloorClean, failed=0, known_red_held=28, known_red_now_passing=0, lane failed=0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5XUhnmnz3EBBQsK9go4W1
@gunbai-bot
gunbai-bot Bot merged commit 72cbda3 into main Sep 1, 2026
5 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/eager-ram-258 branch September 1, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants