Skip to content

compute fabric design - #9009

Closed
briansrls wants to merge 22 commits into
mainfrom
probe/future-main
Closed

briansrls wants to merge 22 commits into
mainfrom
probe/future-main

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session silent-bear-842.
Pushing to probe/future-main 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.

Brian Searls and others added 22 commits August 23, 2026 01:05
…ve the operator both controls, and let the unwired one refuse by name

The operator asked for a capacity visualization AND control in the daily
workspace -- turn a machine down, cut off a provider. This is the
authority both halves read.

WHERE A CONTROL HAS TO BIND TO BE REAL. Both axes meet at
dispatch_selection provider_inventory_for_instance, which returns the
ProviderInventory that selection resolves against. So cutting a runtime
REMOVES ITS OFFER: the resolved selection has no constructor for a
provider that made no offer, which is structural impossibility rather
than a gate. A check placed after selection would concede that the
selected-then-rejected state is writable.

TWO AXES, NOT ONE ENABLED FLAG. Turning a machine down and cutting a
runtime are different questions. Fusing them makes the smaller action
unavailable -- an operator wanting Codex off everywhere would have to
take hosts down to get it. They are two withdrawal rosters over two
subject types.

WITHDRAWAL, NOT ENABLEMENT. Rows name what is CUT OFF, so an unlisted
subject is active. The inverse makes the roster load-bearing for
ordinary operation: a host absent through an authoring slip goes dark.
Withdrawal fails toward capacity remaining available, which is the
recoverable direction -- too much capacity is a cost, too little is an
outage.

THE CONTROL NAMES ITS AXIS EXPLICITLY (operator ruling 2026-08-23).
"Cut off a provider" could mean the runtime or one account binding, and
the conflation is silent in the worst direction: an operator meaning
"cut this Claude account" who gets Claude cut entirely discovers it as
missing capacity, not as a refusal, because both readings are
well-formed. So both controls exist from this first version and the
unwired account axis REFUSES with a typed ControlUnbound carrying the
axis and its trigger. An absent control would read as "not applicable
here" -- the not-applicable-versus-malformed conflation. The axis is not
hypothetical: a credentials update performing an active-slot swap
restarted a live container on 2026-08-22, so it is already being
operated by hand without a control.

The decision takes its rosters as ARGUMENTS, with the global readers as
thin wrappers, because the live rosters are empty and must stay empty --
a decision reading them directly would leave every refusal arm
unreachable by any fixture, making the witnesses decoration that is
cited as coverage.

Green by execution, seven witnesses, plus the mutation proof: collapsing
CapacityRefusedProviderWithdrawn into the host arm turns
a_downed_host_and_a_cut_runtime_do_not_share_one_answer red while
a_withdrawal_does_not_leak_to_its_siblings stays green. Corpus parse
gate rc=0, 0 diagnostics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
… plans, and the gate that makes a cut real lands at the offer (review 54905)

Two changes: the review 54905 repair, and the consumer that makes this
authority non-inert.

THE REPAIR, AND IT WAS THE FAIL-CLOSED FAILURE IN MY OWN DIFF.
apply_capacity_control returned ControlApplied for CutHost,
CutProviderRuntime, RestoreHost and RestoreProviderRuntime while
actuating nothing -- the rosters are module-scope data rows, so a caller
that cut srv3 and then read host_is_withdrawn(srv3) saw false
immediately after being told the cut succeeded. The sibling witness
asserting the rosters stay empty made the two claims jointly
inconsistent by construction. That is DESIGN section 5 fabricated
plausible output, and the existing witnesses could not see it because
they compared outcome strings to each other rather than joining the
control to the roster reader.

WHAT ACTUALLY HAPPENS IS PLANNING, SO THE VOCABULARY NOW SAYS SO.
ControlApplied is deleted; ControlPlanned carries the roster edit that
would effect the request. Withdrawing capacity means authoring a row and
committing it -- deliberately, so a withdrawal faces review like any
other change to what the fleet does -- which is the same plan/apply
split the fleet converge path already uses.

The reviewer's proposed witness is now in tree in both directions:
plan a cut, then READ THE ROSTER. Mutation proof that it discriminates:
relabelling the planned arm "applied:" turns
planning_a_cut_does_not_withdraw_anything_by_itself red.

On the second finding, the Bool predicates are KEPT and the reason is
recorded on them. Present => true / Absent => false is predicate
dissolution and would block in std, but both callers -- the counts and
the dispatch gate -- want the boolean and not the row, so dissolving
would push a match over an Option whose payload is discarded into every
call site. The Option readers are exported beside them.

THE GATE. provider_inventory_for_instance now filters offers through the
capacity authority, so a withdrawn host or runtime MAKES NO OFFER and
ResolvedProviderSelection has no constructor for it. The declared
inventory keeps its own function so the gate has an unfiltered
denominator, and the withdrawn offers are returned separately: an
inventory emptied by withdrawal and one empty because nothing is
installed are different facts with opposite remedies, and a bare filter
renders both as the same empty list -- the empty-observation narrow.

capacity_admission collided with std.materialization_ladder; renamed to
fleet_capacity_admission rather than aliased, since two spellings in one
namespace is the fork.

Green by execution: 9 control witnesses, 4 gate witnesses proving the
gate CUTS (fixture-authored withdrawal, since the live rosters are empty
and a gate reading them directly could only ever be witnessed neutral),
plus 4 pre-existing dispatch_selection witnesses still green. Corpus
parse gate rc=0, 0 diagnostics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…he same roster the dispatch gate reads

The operator asked for live fleet info on the daily workspace. This is
the panel.

IT READS gunbc.fleet_capacity_control DIRECTLY, not a display copy. That
is the whole difference between a control and a cockpit: a panel with
its own roster drifts from the thing it claims to steer, and the drift
is invisible because both sides stay internally consistent.

WHAT IT SHOWS: a summary line (active hosts, active runtimes, committed
slots), a row per host carrying committed width and per-slot memory
ceiling with its capacity standing, and a row per provider runtime. Live
values today: 4 of 4 hosts, 3 of 3 runtimes, 22 slots committed at 16
GiB each.

EVERY NUMBER IS LABELLED COMMITTED RATHER THAN OBSERVED, IN THE RENDERED
OUTPUT AND NOT ONLY IN A COMMENT. runner_slot_allocation states that
srv3/srv4's width of 6 is a provisioning target -- two slots that do not
exist yet -- while srv1/srv2's 5 matches live. A reader summing an
unqualified column gets 22 for a fleet running 20, and a capacity
decision made on that number is wrong on the only axis the panel exists
to inform. An annotation cannot carry the qualifier because no operator
reads the source.

The withdrawn-slots line is ABSENT when nothing is withdrawn rather than
reading zero: a standing "0 withdrawn" row is noise on every ordinary
day and trains the reader to skip the row where a non-zero number
finally matters.

A DEFECT THE WITNESSES CAUGHT, WORTH RECORDING BECAUSE OF WHAT IT WOULD
HAVE DONE. fleet_capacity_withdrawn_line bound its subtraction across a
line break, so the second operand parsed as a separate term and the
count was 22 rather than 0 -- the panel would have announced "22 slots
withdrawn by operator control" on every page load, a fabricated claim on
the operator's main surface, while the fleet was fully active. Found by
nothing_withdrawn_means_no_withdrawn_line, not by reading.

Green by execution, five witnesses. The load-bearing one serializes the
ACTUAL daily workspace document and finds the panel in the HTML -- a
witness over the fragment alone would prove it builds and say nothing
about whether it is composed into the page. The slot total is asserted
as a relation against gunbc_runner_slots_per_host rather than against
the literal 22, so it goes red on a panel that silently stopped reading
the allocation authority. Corpus parse gate rc=0, 0 diagnostics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…is per host, and report the unmeasured one as unmeasured

The panel showed four hosts and a total. A total is the one number that
cannot answer the question an operator actually has -- what unblocks
more capacity -- because a committed width is a MINIMUM OVER AXES and
the surviving number discards which axis produced it.

THE AXES ARE NOT INTERCHANGEABLE PURCHASES. Where memory binds the
remedy is DIMMs; where cores bind it is a different machine. A reader
given only a fleet total assumes one story across four hosts.

THIS IS ABOUT TO MATTER MUCH MORE THAN IT DOES TODAY, which is why it
is authored now rather than after someone reads a total wrong. Measured
now, memory binds everywhere and the column looks redundant. Once the
CPU axis lands (gunbc#8976, relayed by warm-tern-755) srv1/srv3/srv4
become core-bound at 21 while srv2 stays memory-bound at 5 -- its 64 GiB
DIMM install failed training and was reverted, so it holds 8x16 GiB
where the others hold 8x64 -- and the single total becomes two unrelated
stories at 68 committed against 20 running.

THE UNMEASURED AXIS IS REPORTED AS UNMEASURED, NOT AS ADMITTING
EVERYTHING. Disk returns DiskWidthUnconstrained on every host, and
runner_slot_allocation's own reason string is explicit that this is "an
unmeasured axis, not a measured all-clear". Rendering it as admitting
any width would turn an absence of observation into a positive
clearance. It is excluded from the BINDING set by construction: an axis
that states no width cannot be at the minimum.

host_binding_width_axes returns a LIST rather than a winner, because a
tie is real information -- two axes at the same number means relieving
either alone buys nothing -- and picking one would have to break the tie
arbitrarily.

TWO RAW LENGTHS BECAME DERIVED RESERVATIONS. The first revision wrote
min-width 5rem and 9rem, which would have been the only untokened
lengths in the stylesheet and would need re-tuning by eye whenever a
label grew. They now derive the longest string each column can wear plus
a gutter, following roadmap_component dispatch_reserved_width exactly.
The metric column resolves to 35ch from "bound by memory · disk
unmeasured"; a new host or a longer phrase moves it by derivation.

CSS digest re-pinned to b6df98adc93f7077, derived by execution on this
tree per the convention in that file -- never chosen -- with a re-pin
note naming the rule family and its behavioral receipts.

Green by execution: an unmeasured axis is not reported as binding, the
binding axis sits at the committed width (asserted as a relation against
gunbc_runner_slots_per_host, so a label authored independently of the
arithmetic would go red), the panel renders it for every host, and the
digest pin holds. Corpus parse gate rc=0, 0 diagnostics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
… declaration: srv2's nominal bytes were another vendor's

main's last failing witness is fleet_intent_memory.srv2_population_matches_bmc_memory_summary.
It passes under `gunbc run --entry` and fails under the required floor, with both
sides of its equality computing exactly 137438953472 in isolation -- so the
witness is arithmetically correct in both populations and only RESOLUTION differs.

THE SPECIMEN. `stick_capacity_bytes` was declared twice, and the two declarations
are two different facts:

    extdeps.memory.sk_hynix   17179869184   (16 GiB, HMA82GR7AFR4N-VK and -R8N)
    extdeps.memory.samsung    34359738368   (32 GiB, M386A4G40DM0-CPB)

Each was referenced BARE inside its own module. Whichever wins decides what
hma82gr7afr4n_vk_catalog.capacity_bytes is, and srv2's nominal total multiplies
it by eight. Under the floor Samsung wins, Hynix rows read 32 GiB, srv2 nominal
becomes 8x32 rather than 137438953472, and srv3 stays correct because Samsung's
value was right for Samsung's part. Every observed fact fits.

THE MECHANISM, PROVEN BY DISCRIMINATING PAIR RATHER THAN INFERRED. A four-module
fixture under the ENTRY-major resolver, both runs identical but for closure
membership:

    fx.owner     data shared_value = 111
                 fn owner_reads_its_own() { shared_value }   <- bare ref, OWN local decl
    fx.stranger  data shared_value = 222
    fx.puller    imports fx.stranger                         <- drags it into the closure

    stranger in closure   -> owner_reads_its_own() = 222     WRONG, silent, no diagnostic
    stranger absent       -> owner_reads_its_own() = 111     correct

A MODULE CANNOT RELIABLY READ ITS OWN LOCAL DECLARATION. It is silent, it yields
a wrong NUMBER rather than a refusal (DESIGN 5's fabricated plausible output), and
it reproduces in the NARROW four-module pool -- not only under the floor's wide one.

WHAT THIS CHANGE DOES, AND WHAT IT DOES NOT. It removes the SPECIMEN: two facts
sharing one spelling is a DESIGN 3 violation on its own terms, resolver defect or
not. Both files already carried a capacity suffix (`stick_capacity_bytes_64g`), so
the repair follows local convention rather than inventing one -- `_16g` and `_32g`,
both verified unused corpus-wide first.

It does NOT fix the mechanism. The next colliding pair reproduces it silently, and
the census says the exposure is 643 duplicated fn/data names and 84 duplicated type
names. That wall is separate work; the fixture above is its RED and already executes.
A wall scoped to bare CROSS-MODULE references would NOT catch these sites -- they are
local references -- so the candidate set has to include local declarations.

EVIDENCE, AND ITS LIMIT STATED. The witness returns `true` under `--entry` after this
change -- but it did BEFORE it too, so that is a REGRESSION CONTROL, not proof. The
floor is the only consumer that reproduces the failure, so CI is the evidence and this
commit does not claim the close ahead of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HBx8dnz3oCiiHXoSBdtH2
…ome, no consumer, and no declaration

`git add -A` swept `.fx/fx/*.dag` into the previous commit. Six files I authored
as a throwaway probe and never intended to land. Removing them rather than
keeping them, and the reasoning matters more than the six files:

REVIEW READ THEM CHARITABLY AND THAT READING WAS TOO KIND. Review 54981 described
them as "the discriminating probe fixtures for that same ambiguity class... matching
DESIGN's authorable RED at the fixture boundary discipline". They are the right
SHAPE for that, and the pair genuinely executes -- but nothing in the repository
runs them. `.fx/` sits outside both floor source roots (`dag`, `src/v2`), so the
required floor never discovers them. An artifact whose only property is that it
LOOKS like a fixture is the inert-lens tier DESIGN 6 names: coverage by illusion.

WHAT THEY WOULD HAVE ADDED IF THEY HAD BEEN IN SCOPE, which is the sharper reason:
`fx.owner` and `fx.stranger` each declare `shared_value`, so the fixture introduces
A NEW DUPLICATED NAME to the corpus -- one more instance of the exact defect this
PR removes. And `fx.root` declares `owner_binds_its_own_local_declaration`, which
returns FALSE by construction because that is the bug it demonstrates. A witness
that must stay red is not a thing to leave lying in an undeclared directory where
a later change to the floor's roots would enroll it.

NEITHER RISK IS LIVE TODAY -- `.fx/` is out of scope, verified -- but "out of scope
by accident" is not a home. Undeclared, unrun, and unowned is the experimental
residue DESIGN 6 tells reviewers to presume against, and the author's own charitable
label is not the denominator.

The fixture is preserved outside the tree and lands WITH the wall it is the RED for,
in a real home with an executing consumer, or not at all. This PR is the specimen
repair -- two renames -- and its diff now says exactly that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HBx8dnz3oCiiHXoSBdtH2
…olumn it was authored for is now live

Resolved the one conflict in runner_slot_allocation as a UNION rather
than a choice. Both sides added functions at the same point and neither
supersedes the other: main (#8976) added host_cpu_admitted_width and
made the width a minimum over three axes; this branch added the model
that says WHICH axis bound it.

The union is not merely both texts. host_width_axes had to gain the CPU
axis, because a binding-axis fold that enumerated only memory and disk
would report "bound by memory" for a host whose width came from cores --
naming a cause that did not produce the number, which is worse than
reporting no cause at all.

THE COLUMN WAS AUTHORED FOR A FUTURE THAT ARRIVED DURING THE MERGE. Its
own annotation said the axes would diverge once the CPU ruling landed;
it landed. Measured on the merged tree: srv1/srv3/srv4 are core-bound at
21, srv2 is memory-bound at 5 on its reverted DIMMs, and the panel now
reads 68 slots committed against 20 running. That is two unrelated
stories behind one total, which is exactly what the column exists to
keep an operator from misreading.

One witness was FALSIFIED by the merge and was fixed rather than
relaxed: an_unmeasured_axis_is_not_reported_as_binding asserted srv1 was
memory-bound, which stopped being true. Its axis-naming half moved into
a new, stronger witness -- srv1_and_srv2_are_bound_by_different_axes --
which is the discriminating one: a label function naming a single axis
for the whole fleet passes every other assertion in the file and fails
this one.

CSS digest re-derived on the MERGED tree per that file's own resolution
note (a digest pins emitted bytes, and a merge is the one moment when
the bytes belong to a tree neither side built). It holds at
b6df98adc93f7077 -- the longest metric label is still "bound by memory ·
disk unmeasured" at 35ch, so the derived column reservation did not
move.

NOTE, NOT MINE: the corpus-wide parse gate now refuses on
dag/test/claim/build_cache_endpoint_observe_test.dag -- a section 4c
annotation inside a declaration body, last touched by #8976, which this
branch does not modify. Reported to that lane; recorded here so a reader
does not attribute it to this merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ole floor at preparation

Main is parse-broken and it is mine. #8976 landed a four-line // block INSIDE the
match body of witness_retired_runner_slot_owner_refuses. DESIGN §4c admits
standalone leading blocks attached to MODULE-SCOPE declarations and nothing else,
so an annotation indented into a declaration body is a located parse refusal.

WHAT IT COSTS IS OUT OF ALL PROPORTION TO WHAT IT IS. It refuses at PREPARATION,
before any witness runs, so the floor reports:

  required-ci: floor refused: subject=949e339137b087a8 modules_resolved=3827 modules_excluded=4
  required-ci: FAILED PHASE floor

Not one failing row -- every witness in the corpus taken down by a comment's
indentation. It also breaks the local corpus-wide parse gate several lanes use
before pushing, which is how silent-bear-842 found it: their branch went rc=0 to
rc=1 across a merge in which they changed nothing near it.

The annotation is hoisted above the declaration unchanged in substance, and
extended to record where the check does and does not live -- because that is the
part worth keeping. --required-cited-symbol parses the whole corpus and passed
this file CLEAN, twice, before I pushed #8976. Annotation grain is enforced in the
floor's preparation, not in that census, so a green parse is not evidence that an
annotation is placed legally. That is the second time in one night this lane shipped
on a local check that could not see the defect class it was being trusted for; the
first was a line break after `data X: Type =`, which cited-symbol DID catch.

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

Slot standup had its own path: a provisioning plan nobody reviewed, no lease, no
member-set fingerprint, no CAS against an observed baseline. Timers and caps
already flowed through membership_reconcile and slots did not, so the fleet had
two ways to change a host and only one of them was gated. A width change landing
through the ungated one is unreviewed by construction.

This routes slots through the same spine:

- observed_slots joins host, timers, caps and spark in observed_baseline_text,
  the member-set fingerprint and the plan subject, so a slot roster that drifts
  between plan and apply now fails the CAS instead of applying a stale plan.
- fleet_converge_slot_family resolves the deploy row ONCE and both the review
  lines and the apply script derive from it. Resolving twice would let a
  reviewer approve one membership while apply executed another, with both halves
  internally consistent and the bundle hash unable to see the difference.
- slot ADD is emitted into apply.sh. Slot directories live under the runner
  user's own /opt/actions-runner, which is the principal converge already runs
  as -- unlike cap drop-ins under /etc/systemd, which stay plan-output-only
  because they belong to the deploy principal.
- REMOVE and CHANGE emit nothing, because runner_slot_member_ownership returns
  Absent for every observed slot: no provenance signal yet distinguishes a
  gunbc-placed directory from a stranger's. Reconcile yields
  MemberRemovalRefused, the refusals are rendered for the reviewer, and nothing
  deletes a directory whose ownership was never established.
- An unmodeled host refuses in both projections: a REFUSED line in plan.txt, and
  an exit 1 in apply.sh. Silence would be indistinguishable from "this host had
  nothing to do", which is the fabricated plausible output at the actuation seam.
- It counts as one refusal, not zero. Zero reads as "nothing was refused on this
  axis", so the most severe state would have looked like the cleanest one.

The CLI observes slots for real. RunnerSlotsUnobserved refuses the plan and the
apply rather than being rendered as an empty roster -- an empty roster reconciles
to ADD EVERY SLOT against a machine whose population nobody read.

srv1 and srv2 gain deploy rows, which is what made the width authority
actionable on half the fleet: gunbc_runner_slots_per_host could say srv1 carries
twenty-one and there was no provisioning target to hand it to. Addresses were
read over SSH, not assumed -- the lane started with .183/.184, which are the BMC
addresses, and an ssh_target pointing at a management controller fails looking
like an unreachable host rather than a wrong row.

Two witnesses stop pinning six. They asserted a literal copied from the tree the
day they were written, so the CPU-axis change turned them red without anything
about the property they name having changed -- a change detector, not a check
(DESIGN 5). What survives is the relation: the instance roster is exactly as long
as the declared count, at any width. The fleet-wide row asserts an INEQUALITY
against the memory budget rather than equality, because runner_count is the
minimum over memory, CPU and disk while declared_runner_count_for is the memory
axis alone -- since the CPU axis binds first on srv1, srv3 and srv4 the two
legitimately differ, and equality would have been both wrong and vacuous.

Also carries the section 4c annotation hoist from #8989, without which the floor
refuses at preparation and no witness in this PR can execute.
Four defects the local required-floor caught, three mechanical and one that is
the interesting kind.

RunnerSlotsUnobserved carried a host field. observe_runner_slot_members_wet runs
LocalExec against whatever machine it is on and has NO WAY TO NAME THAT MACHINE,
so the only thing that field could ever hold at the observation site was a
fabricated value -- and what I wrote was "" as HostIdentity. That is a
fabrication at a REFUSAL seam, which is the worst place for one: the arm exists
to say "I could not read this host", and it was answering with an identity it had
invented.

It did not typecheck, and the reason is worth recording rather than just fixing.
HostIdentity is a branded NonEmptyStr, so the empty string is not one -- the
carrier's own shape refused the fabrication that a reviewer would otherwise have
had to catch by reading. Construction over validation, working as specified.

The field is REMOVED rather than filled. The observation reports only the cause
it actually established; the caller, which knows which host it is converging,
supplies the identity. runner_slot_converge_verdict already took host as a
parameter, so nothing downstream loses information.

The module's own note claimed "RunnerSlotsUnobserved carries the host and the
cause". That is now false, so it is corrected in place rather than left standing
-- a note describing a field that no longer exists is the stale-citation class,
and it would have been the first thing the next author trusted.

Mechanical, same run:
- trim was called unqualified and unimported; it is std.algebra trim(s:).
- spark_serving_fleet_converge_apply_shell was a second call site of
  fleet_converge_apply_shell that the signature change missed. Threaded rather
  than defaulted -- passing [] there would have made the spark path silently emit
  no slot section while looking like it had one.
Its last CI was red for a reason that is gone: the four-annotation
preparation refusal from #8976, now hoisted by #8989. Nothing here
changed; this only re-bases the evidence onto a main whose floor
actually reaches the fold, so the srv2 witness verdict is measured
rather than inherited.
…ndex instead of writing one down

The CPU-axis change (#8976) turned seven witnesses red. Seven of the eight
failures on main are this, and they are ONE defect rather than seven, which is
what makes retyping the numbers the wrong repair.

EVERY FAILING FIXTURE PICKED AN INDEX THAT WAS OUTSIDE THE COMMITTED POPULATION
AT WIDTH 5-6 AND IS INSIDE IT AT 21.

  a_width_above_the_committed_ceiling_is_refused_not_silently_unfulfilled
    asked for width 7 against a ceiling of 6. At a ceiling of 21, seven is an
    ordinary in-range request the plan correctly fulfils.

  a_github_runner_in_a_fabric_slot_refuses_instead_of_reading_converged
    required srv1 [1, 6] to refuse. Slot 6 was outside srv1's five-wide
    population; at twenty-one it is an ordinary committed member.

  an_identity_outside_the_committed_population_is_refused_not_classified
    read srv1-06 and srv3-07 as outside. Both are now inside.

  introducing_the_fabric_slot_deregisters_no_live_runner
    pinned github_count == 5, which was 6 committed less 1 fabric.

  srv4_enables_six_named_runner_instances
    pinned the roster at six AND asserted srv4-07 is absent -- two measurements
    of the same day, falsified together.

So the refusal arms did not move. The fixtures silently stopped discriminating:
each chose an out-of-range index by writing down a number that happened to be out
of range at the width of the day. Retyping 7 as 22 re-arms the identical landmine
one width later -- a fixture whose RED depends on a number nobody derived is a
change detector wearing a property's name (DESIGN 5: a measurement copied from
the same current tree is not an oracle).

Each now derives its discriminator: committed + 1 for an out-of-range index,
committed - 1 for the GitHub count after the fabric carve, runner_count for the
roster length. They discriminate at any width.

TWO CLAUSES DELIBERATELY UNTOUCHED, because they are the informative half.
srv3-99 is outside at any plausible width. And every srv3-06 clause passed
through the width move without noticing: srv3-06 refuses because it is the
AUTHORED fabric identity, not because of where it sits relative to a width.
runner_slot_allocation was built that way precisely so a width change could not
silently relabel a slot that may be running work, and this is that defence
observed surviving a real width change rather than only asserted.

ONE DELIBERATE NON-GENERALIZATION. introducing_the_fabric_slot_deregisters_no_live_runner
says committed - 1, not committed - host_fabric_slot_count. The general form is
true for ANY number of fabric members including zero, and that conservation is
already asserted by slot_purposes_partition_every_host_committed_width. THIS row
is about the specific carve -- srv3 gives up exactly one slot -- and writing it
generally would leave that claim asserted nowhere.

Two further rows of this class are already repaired in #8992
(witness_srv3_deploy_row_names_six_slots, witness_srv4_runner_count_six_materialization_target).
desired_runner_slot_members mapped runner_instance_names straight through, so
desired was 1..runner_count and nothing asked what any of those slots was FOR.
gunbc.runner_slot_allocation authors srv3-06 as SlotServesFabricExecution, so the
desired set proposed installing a GitHub Actions runner ON TOP OF THE FABRIC
EXECUTION SLOT -- and reconcile would have classified it as an ordinary missing
member, so apply would have done it with no refusal anywhere.

That is the count-as-authority failure runner_slot_allocation exists to prevent,
reintroduced one layer above it. Its own note records the first occurrence:
purpose was derived by comparing index against count, so the fabric slot MOVED
WITH THE WIDTH -- at width 7, srv3-07 would have become fabric and srv3-06 would
have been silently handed back to Actions, relabelling a slot that may be running
work. The carve was made an AUTHORED identity so a width change could not do
that. This module consumed the width and ignored the carve, arriving at the same
outcome by the other road.

THE FILTER IS ON THE FABRIC ROLE, NOT ON COMMITTED MEMBERSHIP, and the first
version of this fix got that wrong in a way worth recording.

Filtering on runner_slot_membership asks the allocation authority whether each
index falls within the host's committed width. That is the question runner_count
already answered, and it returns ZERO desired members for any host the fleet
model does not know. The reconcile fixtures in this module's witness file
deliberately plant a synthetic deploy row (host_label "wfix") precisely so their
expected counts are literals authored beside a known input rather than a
restatement of the live fleet. Routing desired through the global width authority
would have silently emptied those fixtures and turned four exact-count assertions
GREEN BY VACUITY -- manufacturing the change-detector failure this lane spent the
night removing, in the same session.

So width still comes from the deploy row, which already derives it from
gunbc_runner_slots_per_host, and this function removes exactly the identities that
are spoken for. Two questions, two answers: how many slots does this host carry,
and which of them are Actions runners.

It works on INDICES rather than rendered names, so the fabric identity is compared
as an identity and never by parsing a slot number back out of a string it was just
formatted into.

Two witnesses: srv3-06 is absent from the desired set while srv3-05 and srv3-07
are present, and the positive control that only the fabric host loses a target --
srv3 desires committed - 1, every other host desires its full committed width. The
control matters because the first assertion alone is satisfied by a desired set
that is simply too small.
…equired

runner_slot_provision_plan takes an ActionsRunnerBinaryArtifact -- a release
PLUS a published Linux arch -- and fleet_converge_slot_family handed it
actions_runner_release_2_334_0, which is the bare ActionsRunnerRelease. Six
witnesses errored with `no field 'arch' on type 'ActionsRunnerRelease'`,
including four of the five converge witnesses this branch added.

THE ARCH IS DERIVED, NOT AUTHORED. The fix is not a literal Aarch64 in a
production module: the fleet's architecture is a fact of the cited CPU catalog,
so it reads altra_max_m12830_catalog.architecture -- the same authority
host_cpu_admitted_width already reads for core count. A host that is not an
M128-30 therefore cannot silently inherit this arch, because the premise it rests
on is named.

actions_runner_binary_artifact_for_arch returns an option, since an arch with no
published runner binary is a real state. That gets its own refusal arm --
SlotFamilyArtifactUnpublished -- rendered as a REFUSED line in plan.txt, counted
as a refusal rather than as zero, and emitted as an exit 1 in apply.sh. It does
not fall through to a default artifact, which would install some other
architecture's binary.

The two witness fixtures made the same mistake and are corrected the same way,
matching the pattern the older witnesses in that file already used.

WORTH RECORDING ABOUT HOW THIS SURFACED. A wrong argument type on a cross-module
call produced NO diagnostic at parse and none at typecheck; it surfaced only as a
runtime error inside the floor fold. That is the ordinary compiler floor DESIGN
4b requires -- applications bind in exact bijection, fields exist -- and it did
not hold here. Filed as a separate finding; this commit only repairs the caller.

It also vindicates the advice not to keep pushing over an in-flight run
(fierce-hawk-734): this branch had two cancelled runs and zero completed ones,
so the only thing supporting it was my argument that it should pass, and the
argument was wrong.
…ts instead

CI caught a real regression in my own witness after merging main.

WHAT BROKE. The row asserted the panel's total equals
srv1 * 2 + srv3 * 2 -- true only while the fleet was two symmetric pairs.
#8976 made CPU an admission axis and the symmetry ended: srv1/srv3/srv4
became core-bound while srv2 stayed memory-bound on 8x16 GiB, because its
64 GiB upgrade failed training and was reverted. The doubling was never
the property under test; it was a shortcut that happened to hold, and it
turned a genuine fleet asymmetry into a red on a witness about summing.

That the witness went red is correct behaviour -- it noticed the fleet
changed shape. What was wrong is what it asserted.

THE ATTRIBUTION, checked rather than assumed, because a red on my branch
after merging main is exactly the case where blaming main is convenient.
Main's own run 32621117917 at 13db52a fails EIGHT rows
(fleet_intent_memory, runner_capacity_plan x2, runner_host_deploy,
runner_slot_allocation x2, runner_slot_provision x2 -- all downstream of
the same reverted DIMM upgrade). My branch failed NINE. The one
difference is this row, and it is mine.

THE FIX names all four hosts. That keeps the join the row exists to make
-- the panel's total must equal the allocation authority's per-host
widths -- while carrying no assumption about which hosts resemble each
other, so a future asymmetry moves the number without reding the row.

It also stays a real oracle rather than collapsing to
measure() == measure(): the right side reads
gunbc.runner_slot_allocation, a different authority from the panel fold
on the left. Summing the panel's own fold on both sides would have been
the quiet way to make this green and would have asserted nothing.

Verified: this row and the three others in the file return true.

The remaining eight failures are main's and predate this branch.

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

Non-blocking review finding, and it is the absorbing fallback in my own
witness file, so it is worth fixing rather than noting.

empty_workspace_html matched the emission and returned "" on
EmitRejected. That is ⊥-as-answer conflated with ⊥-as-ignorance: every
!string_contains assertion in this file is SATISFIED by the empty
string, so a total serialization failure would have rendered the
negative rows green, while the positive rows could not distinguish "the
panel is missing from the page" from "nothing serialized at all". Two
states with opposite remedies collapsed into one answer, and the
collapse fails in the quiet direction.

Three changes, none of which widen:

The emission is now its own function returning the typed result, so the
refusal is available rather than discarded at the point of use.

The rejected arm carries the reason instead of vanishing, prefixed with
a marker no assertion in this file searches for -- so a positive
assertion fails on it, the text names what happened, and the marker
cannot accidentally satisfy an assertion either.

The refusal gets its own row. Without one it is only ever observed
indirectly, through whichever assertion happens to notice the page is
not what it expected, and the ledger would read "the panel is absent"
for a run where nothing was emitted. the_workspace_actually_serializes
makes it a finding with its own name. The one row carrying a negative
assertion over the HTML now also rests on the emission having succeeded.

The reviewer's second note -- host_is_withdrawn / provider_is_withdrawn
being Present => true / Absent => false -- I am leaving as it stands, and
the reviewer read it the way I intended: both real callers want the
Bool, the Option accessors are exported beside them, and dissolving the
predicate would push a discarding match into every call site. The module
already flags the tension; that is the honest state rather than a
resolved one.

Verified: all four rows over the serialized workspace return true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ns the indexing contract

Both replacements in this file assert list_length(names) == runner_count and
nothing else. A GENERATOR EMITTING ONE NAME N TIMES SATISFIES THAT PERFECTLY
while deploying a single runner N ways -- N units all pointing at one slot
directory, one registration name contending with itself. The count check cannot
see it, because the count is right.

Found by silent-bear-842, who independently rewrote these rows and asked what a
length assertion does NOT cover before shipping rather than after. It is a hole
in my rows, not a refinement of them.

roster_names_are_distinct is a named function rather than an inlined clause
because it belongs beside every length claim in this file, not just the one that
prompted it. Applied to all three rosters.

THE INDEXING CONTRACT IS BACK, ON GROUND THAT CANNOT MOVE.

The rows this file used to carry asserted srv3-01 and srv3-06 by name: one-based,
zero-padded, last index equals count. Those were real claims and I deleted them
along with the width literals they were entangled with, because every index
expectation was a function of a width the CPU axis then moved. Re-deriving them
from the width would have made them vacuous -- the enumerator checked against
itself.

An AUTHORED runner_count breaks that circle. Three is a number the allocation
authority never produced and never will, so the fixture pins one-basedness,
zero-padding and the count-to-last-index relation without consulting the fleet at
all. That is the planted-fixture side of the split ruled by swift-badger-524: a
fixture authoring its own input SHOULD carry literals, and only a probe aimed at
a live authority must derive its discriminator.

MEASURED DISCRIMINATING RATHER THAN ASSUMED. silent-bear-842 planted a one-off in
runner_instance_names (mapping over runner_count - 1) and both length rows
returned false, so the chain from width to count to names crosses a real
enumerator these rows do notice. That planted-defect measurement is the method I
named as this class's next-rung trigger in my own census and then did not run.
@gunbai-bot

gunbai-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closing: this was a local throwaway probe branch that the dashboard auto-opened as a draft PR. It contains merges of three other lanes' branches (#8987, #8992, #8998) into mine purely to answer one question locally — whether my capacity panel's widths survive those PRs landing. They do: widths stay 21/5/21/21, total 68, and my witnesses stay green. Nothing here is intended to merge. — sent from silent-bear-842

@gunbai-bot gunbai-bot Bot closed this Aug 23, 2026
@gunbai-bot
gunbai-bot Bot deleted the probe/future-main branch August 23, 2026 09:41
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