Repository navigation
Conversation
…ectation-versus-observation, and keep "never read" out of "confirmed" The operator asked for live fleet info on the daily workspace. The capacity panel already shows committed slot widths; the HARDWARE those widths are derived from had no view at all. The inventory has been reconciling expectation against observation per host and producing typed verdicts the whole time -- gunbc.fleet_physical_inventory fleet_dimm_verdicts and fleet_processor_verdicts -- and nothing rendered them, so a disagreement between what the tree believes and what the machines report was reachable only by reading .dag source. THE COST OF THAT WAS ALREADY PAID. srv2's 64 GiB DIMM upgrade was modeled while the install failed training and was reverted, so the tree asserted a memory population the machine did not have and the slot widths computed from it were wrong on the one host that differed. The verdict existed and said so. Nobody could see it. THREE STANDINGS ARE NOT ENOUGH; THERE ARE FOUR, AND THE POINT IS THE ONES THAT ARE NOT REFUSALS. Confirmed and Refused are the obvious pair. UNREAD says no reading was taken, so there is nothing to disagree with. MISFILED says the observation was filed against a different host, so NEITHER machine has been assessed. Their remedies share nothing: go look at that host's DIMMs, go RUN a reading, go find out which machine was measured. Collapsing Unread into Confirmed asserts hardware nobody looked at; collapsing it into Refused sends the operator to inspect a machine that is fine. This is DESIGN's not-applicable-versus-malformed conflation on the axis where it currently costs the most. MEASURED, live, on the real fleet: 4 of 4 memory confirmed · 0 of 4 processor confirmed srv1..srv4 memory confirmed 8 sticks as expected srv1..srv4 processor unread no per-host processor reading exists... EVERY PROCESSOR ROW IN THE FLEET IS UNREAD. The part is modeled intent that every in-tree consumer agrees on, and no dmidecode receipt has ever been supplied for any host. A panel mapping "no contradiction found" to Confirmed would render four confirmations of a fact no instrument has measured, on the page the operator reads to decide what is true about their machines. THE DISCRIMINATING RED, measured rather than asserted. Installing exactly that collapse (ProcessorModelUnconfirmed => HardwareConfirmed) in a copy of the tree: FALSE every_processor_row_is_unread_and_none_is_confirmed true the_memory_axis_is_confirmed_on_every_host true the_four_verdict_shapes_map_to_four_distinct_standings true the_four_standings_do_not_share_a_wire_word true a_refusal_detail_carries_the_count_and_the_discrepancy_tally FALSE the_summary_reports_each_axis_separately_and_never_one_fraction Four of six pass the collapsed mapping. All eight witnesses return true on this branch. THE SUMMARY IS PER AXIS AND NEVER ONE FRACTION. Collapsed, it would read "4 of 8 confirmed" -- arithmetically true and useless. Memory is fully read; the processor axis has never been measured once. A solved problem beside an unstarted one, and averaging them hides both. Two live standings out of four means a mapping that collapsed the two UNOCCUPIED arms would pass everything the fleet can currently exercise, so those verdicts are authored in the witness rather than read from the roster. Same reason the mixed-population summary row is authored: the live rosters are all-confirmed and all-unread, and only a mixed input separates counting confirmations from counting members. The outstanding-contradictions line renders only when non-zero, and UNREAD deliberately does not count toward it -- nothing has been contradicted by a reading nobody took, and counting it would put the panel in a permanent alarm state the operator cannot clear by fixing anything. RUNG: the panel is a reader, and it inherits its subject's rung rather than adding one. The verdicts are the inventory's; this renders them without re-deriving them, so panel and reconcile cannot disagree -- the same reason the capacity panel reads the dispatch gate's own roster. Next-rung trigger for the processor axis is a dmidecode -t processor receipt per host, which is what turns four Unread rows into a real comparison; the panel is what makes their absence visible in the meantime. Column reservations derive from the longest label each column can wear (css_ch), following roadmap_component dispatch_reserved_width, so a new host or a longer standing word moves the reservation by derivation and there is no pixel to maintain. Local parse gate: 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…the CSS digest by execution
Two things the panel commit owed, and one real defect that only the
whole-page consumer could find.
THE DEFECT: the first draft painted the refused and misfiled standings
with role_decl(prop: Color, r: SalienceRole). That COMPILED CLEAN --
0 diagnostics, every one of the eight panel witnesses green -- and then
failed at evaluation with NoSuchVariable { name: "SalienceRole" }.
SalienceRole is a TYPE in gunbc.design.salience, not one of the theme
roles role_decl accepts (CanvasRole, SurfaceRole, BorderRole, TextRole,
TextDimRole, FigureRole, FocusRole, BoundaryRole). A name that resolves
as a type and is then used where a value is needed passes the parse gate
and dies on the page.
Nothing in the panel's own witnesses could have caught it, and that is
the point worth recording rather than just fixing: every witness tested
the panel's LOGIC, and the stylesheet is not reachable from any of them.
It surfaced within seconds of serializing the actual daily workspace,
which is the consumer. A green witness file is not evidence the page
renders.
THE FIX is not a new role. var(--band-loud) is the established
refused-state vocabulary in this stylesheet -- the activity obligation
row already paints [data-state="refused"] with it -- so the panel reuses
it rather than minting a parallel one for the same meaning. Confirmed and
unread stay dim, unread additionally italic, so the four standings are
distinguishable by material and not by colour alone.
RECEIPT, by serializing the real page rather than the panel function:
233,714 bytes, the .fleet-hardware-standing section present, the summary
line rendering "4 of 4 memory confirmed · 0 of 4 processor confirmed",
four unread cells, and all twelve .hardware-* rules emitted. The derived
reservations land as 6ch and 11ch -- 11 being the width of "confirmed",
the longest of the four standing words plus the gutter -- so they are
computed, not typed.
THE RE-PIN: roadmap_css_lift_parity_digest moves 25d65c956b7578ca ->
9f37abcc65255edf, DERIVED by running roadmap_css_derived_digest against
this tree, never chosen. The accompanying note states what moved and why.
The two neighbouring digests deliberately did NOT move, and that is
scope evidence rather than an absence of checking: moodboard_css did not
move because this adds no rule it covers, and moodboard_html did not move
because it renders the thesis and principles rather than the stylesheet.
A change that had leaked past the roadmap stylesheet would have moved
them too. Both re-run green here alongside the re-pinned one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ostnames (review 55065) `.hardware-axis` took `hardware_subject_reserved_width()` — copied from the column beside it — so a column carrying "memory" and "processor" was reserved to the width of the longest HOST LABEL. Two unrelated populations that happen to be adjacent, and the derivation read the wrong one. Emitted 6ch for a column whose longest word is nine characters. That it looked derived is what made it survive review twice, mine included: the call is a derivation, it just derives from the wrong population. A hardcoded `11ch` would have been more obviously wrong. FIXED BY GIVING THE AXIS WORDS AN AUTHORITY. `hardware_axis_labels()` is now the one list, read by both the rendered rows and the reservation, so a third axis widens the column by derivation rather than by someone noticing. Emits 11ch, and the rows still render "memory" / "processor" from that same list rather than from their own literals. THE WITNESS ASSERTS THE DISCRIMINATING FACT, NOT THE TAUTOLOGY. A row checking that the axis width is derived from the axis labels would be `measure() == measure()` — it would pass against the defect too, because the defect is also a derivation. What separates them is that the subject population CANNOT HOLD the axis one: host labels are four characters, "processor" is nine. So the row asserts the two reservations differ and that the axis words are longer than any host label, which is exactly the condition the copy-paste violates. Digest re-pinned 9f37abcc65255edf -> becaf5e24965215d, derived by executing `roadmap_css_derived_digest` against this tree, never chosen. `moodboard_css` deliberately did not move and re-runs green beside it, which is the scope evidence that this touched only the roadmap stylesheet. Local parse gate: 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
|
Investigated. Nothing on this branch is broken and there is no fix to push here. Run
Same identity, same cause, on main run The repair is #9012, which splits that row's population — measured whole-module against main and against my own competing #9016, that split is the only one of the three that takes the module's max under the cap with real headroom (3420ms vs 4415ms vs 6161ms local). I have recommended #9012 land and #9016 close. This PR unblocks when it does; no commit from me will change its result before then. — sent from silent-bear-842 |
…s the merge made silently Merges #8955 #8967 #8981 #8997 #9000 #9005 #9008 #9013 into one branch. Three resolutions carried real decisions, and two of them were invisible to git: - product.fabric.supply offer_fungibility_for: #8981 rewrote the body while main renamed Offer<P> to SupplierOffer<P>. Kept the new body on the current type name; unioned the import list. - gunbc.roadmap_style: #9005 and #8967 each added a panel in the same region. The first union interleaved the two rule sets into a file that parsed as garbage (expected LParen, found Ident) with NO conflict markers present. Redone as a real 3-way and checked at the block boundary. - roadmap_css_lift_parity_digest: both branches re-pinned it against a stylesheet holding only their own rules, so NEITHER value describes the merged sheet. Taking a side would have pinned a digest no emission produces. Re-derived by executing roadmap_css_derived_digest against this tree: e1434344ca3877e4. And one break git merged cleanly into a third file: #8960's t_offer_with_quantum predates #8981's required Shape.envelope and SupplierOffer.isolation, so the literal lost its type. Both branches were green alone; only the merged tree refuses. Repaired in the same style as t_offer beside it. Verified on this branch: 0 blocking parse errors (the 52 source-annotation diagnostics are pre-existing on main, same file, same count, measured with the same probe against a clean checkout); every witness in every changed test file passes.
|
Closed in favour of #9023, which consolidates this PR with the other seven at operator request. Nothing here is dropped — the full diff is merged into that branch, and the merge resolutions it required are documented in its body. Closing eight approved branches into one rather than rebasing each: all eight were mergeable against a main that has since moved several times, and #8981 had already gone to CONFLICT. Rebasing serially would also have re-opened the same shared regions independently — which is how the — sent from silent-bear-842 |
…two breaks the merge made silently (#9023) * Subsume the fleet's disk reclaimer: model what has been holding slots at 2.5GB, before anything trusts a slot-width number A load-bearing fleet capacity mechanism runs on every runner host and has no representation in the substrate. ctrl-runner-reclaim.timer has been reclaiming runner workspace disk every 6h since 2026-07-23, and it is the reason slots measure ~2.5GB instead of the ~13GB its own header describes. Nothing in the corpus models it. READ FROM THE ARTIFACT, NOT FROM A DESCRIPTION OF IT. The model is derived from reclaim-runner-disk.sh and its .service/.timer, following the precedent runner_lifecycle_ctrl_void_note sets for exactly this case: read the ctrl artifact as ground truth, subsume it here, never invoke ctrl from gunbc. Reading it surfaced a fifth concern that a summary of the mechanism had dropped, and it is the sharpest one -- a review killed mid-run leaks a refs/heads/review/* ref, and once its object goes missing git gc dies AND writes a .git/gc.log that DISABLES ALL FUTURE AUTO-GC. The failure is self-perpetuating: the thing that would reclaim the disk is what the wedge turns off. NOT A SECOND HYGIENE AUTHORITY, and this is worth stating because the two were already being conflated. gunbc.host_hygiene_reaper reaps residual SLOTS -- stale cgroups, inactive units, control-override dirs -- and carries zero coverage of packs, repack, tmp_pack, _diag or docker; verified by grep against it and its _remediate half, all six terms zero. One word, "hygiene", over two mechanisms. So this is net-new modeling rather than wiring up an inert successor, and the module says where the boundary is. WHAT IS MODELED: the five growth paths as typed concerns; slot job state as a THREE-way fact (running / idle / unobservable) because "a job is running" and "we could not tell" refuse alike and repair differently; the repack decision as four arms rather than a Bool, so a consumer can tell "already packed" from "we did not look"; and the script's operator overrides carried as data rather than restated in prose. EVIDENCE, with the mutation that proves it discriminates. Seven witnesses green by execution. The control asserts the two refusals do not collapse to one answer; mutating the production arm so the unobservable case returns RepackRefusedSlotBusy turns that control RED (false) while its sibling stays GREEN (true) -- so it catches that specific defect rather than everything reddening. The fragmentation gate is asserted at both sides of the boundary, and the same 8GB checkout appears in the warranted and skipped rows so a gate that had drifted to charging on size would fail. WHAT IS DELIBERATELY NOT DONE: retiring runner_slot_disk_budget_per_slot. That literal is circular -- byte_size(40960000000), derived by its own annotation as "~38GB per slot at width 7 on 500GB class disk", which is the disk divided by the width it is then used to preflight -- and it measures ~16x observed occupancy. slot_observed_bytes is the observed quantity that replaces it. Per operator sequencing 2026-08-22 the reclaimer is subsumed first, because the width numbers are downstream of whether reclamation runs at all. Parse gate clean over 3881 modules. * Two age floors were bare Int days beside a Second: ground the day on the cited ISO 8601 authority (review 54879) review 54879 raised two findings on host_disk_reclaim, both real. reclaim_diag_max_age_days and reclaim_tmp_pack_min_age_days were flat Int scalars whose unit lived only in the identifier, sitting directly beside reclaim_per_repo_timeout: Second doing it correctly -- one module, two spellings for one concept. Adding Day to std.measure's Scale was the wrong repair: Scale is a closed coproduct, so a new member forces exhaustiveness churn across a load-bearing std module for one downstream field. Instead the day is grounded where the day already is cited -- extdeps.units.iso8601 gains iso8601_hours_per_day(), and std.measure composes seconds_per_day() from it exactly as minutes_per_hour() already delegates. Both fields now carry Second. Second finding: ObservationVerdict/UnknownRefused were imported and never used. Dropped, with an annotation recording WHY SlotJobState is not grounded on ObservationVerdict rather than leaving the next author to re-derive it -- ObservationVerdict answers whether a subject agrees with its desired state; SlotJobState answers what a slot is doing right now, as an input to a decision. A busy slot is not drifted, and reporting it as a convergence verdict would make correct operation read as a fault. Green by execution: the_two_refusals_do_not_share_one_answer -> true, the_fragmentation_gate_is_exclusive_at_both_sides -> true, and a new the_age_floors_keep_their_authored_ratio -> true. That one asserts the RELATION (diag floor is 3x the tmp_pack floor, both positive), not 259200 seconds: a literal transcribed from the same arithmetic that produced it is a change detector, red on a correct unit refactor and green if both floors were wrong by the same factor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The fleet's disk reclaimer was installed by a script this repository could not see: model its service and timer, and teach systemd's authority the three directives that make it safe MVP step 2. gunbc.host_disk_reclaim models what the reclaimer DECIDES; this models how it is INSTALLED. The two are separate facts, and before this commit the second was authored nowhere in the repository -- a load-bearing, fleet-wide mechanism whose units were delivered by a shell script, so nothing here could observe it, converge it, or notice it had stopped. The extdeps extension is the load-bearing half. SystemdServiceDirective could not express Nice=, IOSchedulingClass= or TimeoutStartSec= -- the exact three directives that let maintenance share a host with live CI. A renderer missing them still produces a unit that RUNS; it simply competes with production for CPU and disk, so the omission is invisible in the rendered text and surfaces as someone else's latency. IOSchedulingClass gets a closed coproduct rather than a NonEmptyStr because the kernel closes that value set at three, and an authority that closes a set then carries it as a string has re-opened it. Two directives on the incumbent are deliberately NOT rendered. Documentation= points into the ctrl repo's copy of the script and goes stale on subsumption. EnvironmentFile=-/etc/default/ctrl-runner-reclaim is dropped for a stronger reason: it is an escape hatch of exactly the kind DESIGN section 5 forbids -- RECLAIM_GIT_GC disables the repack and RECLAIM_GIT_GC_MIN_PACKS moves the fragmentation gate, both of which are data rows here. A host carrying that file answers a different policy than the one modeled, silently. That is a deliberate behavioral difference from the incumbent, not a fidelity gap, and it has a standing witness because re-adding the line looks like an improvement. ExecStart still names the installed script. The terminal form is our own binary running slot_repack_decisions with no shell in the path, but that subcommand does not exist and a unit naming it would install a mechanism that cannot run -- strictly worse on a fleet whose disks fill without it. This is the gap-intolerant staged half of the replacement migration, taken deliberately, with its next-rung trigger named on the seam. Green by execution, six witnesses: the three resource directives reach the rendered text; the outer timeout is derived from the per-repo budget (asserted as the relation, not as 3600, so it cannot silently stop tracking); the derived value reaches the unit; no environment hatch is present; the timer drives the service persistently with jitter; and the three I/O classes do not collapse to one wire word. Corpus parse gate rc=0, 3884 modules, 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * "Cut off a provider" names two actions with different blast radii: give 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 * Four control arms reported an effect that never happened: the control 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 * Put the fleet on the daily workspace: capacity at a glance, reading the 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 * Regen the three drifting stage0 mirrors: two are this PR's own seed-closure edits, the third is inherited from main CI's regen phase refused at 38e95f3b14 naming three generated surfaces. Reproduced locally to a fixed point rather than guessed at. TWO ARE MINE AND ARE THIS PR'S OWN OBLIGATION. Adding iso8601_hours_per_day to extdeps.units.iso8601 and hours_per_day / seconds_per_day to std.measure -- the review 54879 unit-modeling repair -- put this change inside the v1 seed closure, so both modules owe a regenerated Rust mirror. The diffs are exactly those additions and nothing else. THE THIRD IS INHERITED AND IS REGENERATED, NOT AUTHORED. v1_compiler_emit_rust.rs drifts on main independently of this branch (three redundant `.clone()` removals from an emitter change whose committed mirror went stale); the drift was confirmed present on origin/main and on branches that do not touch the path. Regenerating a generated file is mechanical and idempotent -- if the owning lane lands the same regeneration the content is identical -- so this is installing the emitter's current output, not taking over someone's repair. Verified by execution rather than by inspection: claim_executor --required-regen refused with the same three files before, and returns first_generation_equal=true rc=0 after. The emitter was rebuilt from this branch's HEAD rather than reusing a stale binary, because main's emitter had itself moved -- generating mirrors with an old emitter is how you install an artifact CI then rejects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Isolation becomes a fabric guarantee an offer can fail: seven named axes, a refusal that says which one, and the measurement that today's slots could not host a tenant Operator direction: slots need to be mutually exclusive, containerized, and ephemeral. This is the core half -- what work REQUIRES and an offer PROVIDES -- and it deliberately does not name a container. THREE GUARANTEES, NOT ONE, AND THIS FILE OWNS ONLY THE MIDDLE. Allocation exclusivity (may two grants reserve one cell) belongs to the durable grant store; a container provides none of it, and two brokers can each start one on the same cell. Ephemerality and sanitation belong to the lifecycle. Isolation -- what a run can observe once started -- is this. Conflating the sandbox with the exclusion wall is how a fabric double-books hardware while every run looks correctly isolated. THE MECHANISM IS ABSENT ON PURPOSE. OCI, nspawn, microVM and dedicated host are realization handlers; a core that named containers could not admit a supplier satisfying the same guarantees another way, which is DESIGN section 3's rule that transport sits outside the interface. THE MATCH RETURNS THE MISSING GUARANTEES, NOT A BOOL. "Not isolated enough" is unactionable; "cannot provide DedicatedKernel" tells a broker which supplier class to find. A Bool would collapse a shared-kernel host and a host with no namespacing into one answer with unrelated remedies. A MODELING ERROR I MADE AND CORRECTED BEFORE COMMITTING, because it is the more instructive half. I first gave the floor a FreshWritableRoot + AttemptScopedSecrets requirement and made the fleet-offer fixture claim the full sandbox profile so it would pass. Both were false: the floor has run green for months on slots providing neither, so the requirement invented a gap that does not exist, and the fixture asserted guarantees current_runner_slot_profile measures as absent -- a fixture lying to stay green. Corrected: the floor requires nothing and says why, the fixture states what the fleet actually provides, and the gap moved to tenant_workload_isolation_requirement, where it is real. CONSUMPTION STATUS IS DECLARED, NOT IMPLIED. offer_fungibility_for has no production consumer -- as its siblings ShapeNotCovered and CapabilitiesNotOffered already record -- so this is a modeled decision the broker will consume, not a live wall. The field and the arm that reads it land together, per the standing rule in fabric_witness_run after the inert-carrier lens caught an earlier declared-but-unread value. Green by execution, six witnesses. The gap witness asserts the COUNT of missing tenant guarantees (4) so that providing one turns it red rather than surviving partial progress, and it is paired with a control that today's slots STILL satisfy our own floor -- without which a match that refused every pairing would look like a working model. Adding the arm forced exhaustiveness at two existing match sites, which is the closed coproduct doing its job. Corpus parse gate rc=0, 0 diagnostics. NOTE FOR gunbc#8960: that PR renames Offer -> SupplierOffer. This adds a field and a fungibility arm to the same type against current main; whichever lands second carries the other through, and the content is mechanical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * A committed width cannot say what would buy more: name the binding axis 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 * Hoist the isolation match out of the guard: it was computed twice on one input (review 54965) The guard and the IsolationNotProvided payload each called unsatisfied_isolation_guarantees on the same two arguments, so the fold ran twice per refusal. One let binding. Fixed rather than waved off as a nit on a two-element list, because DESIGN section 6's bare-minimum-cost rule is explicit that a proven cost-shape defect is always fixed regardless of the realized n: "n is small here" is not a time-stable fact, and pricing per-site exceptions is itself the redundant work the rule exists to avoid. The realized n grows with the guarantee set and with the offer roster a broker will eventually fold this over. Green by execution: the fleet offer is still eligible for the floor, a foreign trust domain still refuses despite ample capacity, and the shared-kernel offer still misses exactly [DedicatedKernel]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * An 8 GiB x64 workload was fungible with a 6 GiB arm offer: fold the resource envelope into the shape, and stop the identity dropping axes the matcher compares Reported by neat-heron-312 from the first production supplier binding (product.supplier.ubicloud), where memory and disk surfaced as UnexpressedSupplyFact. That is honest diagnosis and it does NOT repair the decision: a sidecar saying "memory was dropped" cannot route work. THE WRONG ANSWER, now executed as a witness. offer_covers_shape compared threads and nothing else, so an 8 GiB x64 need was declared FUNGIBLE with a 6 GiB arm offer whenever thread counts and the textual capability ref matched. The broker routes work to a machine that cannot run it and nothing downstream refuses, because fungibility already said yes. NO SECOND ENVELOPE WAS MINTED. gunbc.fleet_container already owned the requirement vocabulary -- CpuRequirement with its architecture axis, GpuRequirement, Memory, Storage, Network -- inside a model of OUR fleet's containers. Those are machine questions, not container questions: a bare-metal host and a rented VM answer them identically. So the five types moved verbatim to product.fabric.envelope and fleet_container now consumes them. A fact's home is its layer. EACH UNMET AXIS IS NAMED, for the reason the isolation match gives: "does not fit" is unactionable, "needs 8589934592 bytes, offered 6442450944" tells a broker which supplier class to find. Architecture is EQUALITY, not order -- there is no sense in which a bigger arm machine covers an x64 need -- and an axis the work does not state is skipped without being reported as checked. THE HALF A FUNGIBILITY FIX ALONE WOULD HAVE MISSED, and it was my own defect one PR earlier. work_identity_material hand-lists fields, and the isolation profile I added in #8981 never reached it -- so two works differing ONLY in what they require of their executor derived the SAME key and deduplicated onto each other, while fungibility compared the field. Identity and matching disagreeing about whether two things are the same work is exactly the divergence that module's own note records for threads. Both isolation and the envelope now feed the digest, with absence rendered as its own token so "no memory requirement" and "a requirement of zero" cannot collapse. ShapeNotCovered is DELETED rather than kept beside its replacement. It carried one axis, which ThreadsNotCovered now carries alongside the axes it could not express; nothing constructs it, and a variant every consumer must match and no producer can emit reads as coverage (section 4b(4)). Green by execution, seven witnesses: the reported wrong answer refuses naming BOTH failing axes, a larger same-arch offer still covers (positive control), a larger ARM offer does not cover an x64 need (the asymmetry that proves equality rather than order), an unstated axis neither refuses nor claims a check, and three material witnesses that memory, architecture, and stated-versus-unstated each change the work key. Four pre-existing fabric witnesses still green. Parse gate rc=0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The parse gate has a false-green mode, and this document caused it: the indexing line is not the verdict Three lanes adopted this recipe today on the strength of one clean run. Two of them hit failure modes the document did not describe, and one of them corrects a mechanism claim the document asserted. THE FALSE GREEN, which is the reason to fix this now. The run prints "indexed 3884 modules from 2 source roots" and SUCCEEDS at that step; the annotation-grain and parse diagnostics arrive after reconcile, at the very end -- 128 seconds on the box that measured it. Anyone who starts the command, sees a clean indexing line at 95 seconds and interrupts reads a defect-free tree that is not defect-free. That is worse than having no gate, because they will have "run the check". neat-heron-312 came within a minute of reporting the recipe as broken for exactly this reason. THE MECHANISM CLAIM WAS WRONG AND IS CORRECTED IN PLACE. This document said annotation grain is checked DURING INDEXING, and I repeated that to two other lanes in messages. It is not: indexing reads the sources and the refusal is raised later. What survives is the part that matters -- the diagnostic fires for modules the entry never imports and never compiles -- and it now rests on a measurement rather than on my reasoning about phases. THE GATE NOW HAS BOTH ARMS. A check that has never gone red is a decoration, and this one had only ever been run green. neat-heron-312 ran a discriminating pair on one tree with one binary: a planted in-body // in dag/product/workload_simulation.dag gives EXIT=1 with the located diagnostic, reverting gives EXIT=0 and 0 diagnostics. The planted defect sat in a module the trivial entry does not import -- six files compiled, 3884 indexed, diagnostic from one of the 3878 never compiled at all. That is stronger evidence for the trivial-entry trick than the argument that motivated it. THE STALE-BINARY MODE IS DOCUMENTED because it is the most expensive way this can go wrong. A binary predating --entry refuses the flag; falling back to a whole-corpus compile without an entry selects a parser that rejects // outright and returns ~25964 errors on a CLEAN tree (warm-tern-755, confirmed against a stashed HEAD). The degraded mode is loud and its output reads as a discovery rather than as a broken instrument -- the absorbing fallback with the failure wearing the costume of an answer. Five-figure error counts mean check your binary. Also recorded: the local gate locates by character offset while the floor gives file:line:col for the same class, so a mismatch against line numbers is expected rather than a broken grep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Re-point the one citation the envelope move outlived (cited-symbol gate) Moving ResourceEnvelope from gunbc.fleet_container to product.fabric.envelope left a DeclarationRef in gunbc.doc_graph_roots naming the old home: cited-symbol: REFUSED DECLARATION-ABSENT gunbc.fleet_container ResourceEnvelope cited-symbol: FAIL 1 authored reference(s) do not resolve — a citation outlived what it names (DESIGN §3) Re-pointed to the new module. The declaration name and field are unchanged, so the citation names the same thing it always did. WORTH RECORDING RATHER THAN JUST FIXING: this is the §3 citation rule working exactly as designed, and it caught something no other gate would. The floor was green, the parse gate was green, every witness was green -- because nothing EXECUTES a doc-graph citation. It is a symbolic reference in a hand-authored plan binding, and the only thing that resolves it is the census built for that purpose. It is also the argument for symbolic citations over positional ones, made by execution rather than by assertion. A file:line pointer at the old location would have gone silently stale in the same move, with nothing able to detect it: a line offset is not reachable from the namespace tree, so no census could have refused it. The name was decidable, so it was refused, located, in one line. Verified: claim_executor --required-cited-symbol returns rc=0, "every authored reference resolves checked=390", where it refused before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * A cell returns to supply only after teardown is proven, not when the process exits The operator asked for ephemerality: cleanup after customer jobs, fresh spawn with caching on every run. The load-bearing question underneath it is not HOW to clean but WHO DECIDES THE CELL IS CLEAN, and the tempting answer -- the job exited zero -- is wrong in a way that leaks one tenant's state into the next one's run. A job can exit zero and leave a detached child holding a mount, a populated writable layer, a secret tmpfs, or a live network namespace. So the exit status is an INPUT to sanitation and never its verdict, and this models the verdict. THE SPLIT THAT MAKES "FRESH SPAWN WITH CACHING" COHERENT rather than self-contradictory: the CELL (srv3-06) is a schedulable identity that persists across many attempts; the SANDBOX exists for exactly one attempt and none survives into the next. Fresh WRITABLE state per attempt, reused IMMUTABLE material underneath, and tenant code never edits the cache in place. SIX SEPARATELY-OBSERVABLE FACTS, NOT A `cleaned: Bool`. Each fails independently and each leaks something different, so a single boolean lets any one of them fail invisibly behind the others succeeding. An observation is three-valued, and the third value is the point: Unobservable says the readback could not REACH the fact. Collapsing it into Refuted would be safe but reports a failure that did not happen; collapsing it into Confirmed is how residue reaches the next tenant. They are kept distinct because their operator REMEDIES differ. THE DEFECT THIS IS SHAPED AGAINST is DESIGN's empty-observation narrow: a readback that confirms five facts and simply omits the sixth. A fold walking the OBSERVED list concludes "nothing was reported wrong" from a report that never covered the subject, and hands back a cell carrying the previous tenant's working tree. So the fold walks the REQUIRED list and demands an observation for each. Same narrow one level out: a host agent lost mid-attempt produces no readback at all -- exactly when the cell is MOST likely dirty -- so that yields every fact unobserved and quarantines with a full list, rather than nothing-to-clean. MEASURED, not asserted. Installing that precise defect in a copy of the tree and running all six witnesses: true a_fully_proven_teardown_returns_the_cell FALSE five_confirmations_and_a_silence_do_not_return_the_cell true an_unobservable_fact_refuses_reuse_without_claiming_teardown_failed true a_refused_teardown_names_the_fact_and_its_detail FALSE a_lost_host_agent_quarantines_with_every_fact_unproven true the_six_facts_do_not_share_a_wire_word Four of six pass the broken fold. That is the receipt for why those two witnesses exist and why a green suite here is not self-evidently meaningful: only the pair that walks the required list can see it. All six return true on this branch. The attempt's outcome is not a parameter of the verdict, and its ABSENCE is the enforcement -- there is no argument through which an exit status could arrive, so a caller cannot let a successful run stand in for a proven teardown (§5 construction over validation). An earlier draft also carried a same-signature forwarding function restating that in its name; it was a hollow alias with no caller, so the rule moved to the real function's header and the alias is gone. RUNG, honestly: this is a MODEL, and nothing observes a real cgroup yet -- current_runner_slot teardown is unmodeled and the readbacks here come from fixtures. The verdict logic is structurally guaranteed (a cell cannot be returned without a confirmation per required fact, because the constructor demands the list); the OBSERVATIONS are at mitigatable, since a host agent could report a confirmation it did not establish. Next-rung trigger: binding SanitationObservation to a real per-attempt readback on the host agent, at which point FactUnobservable stops being a fixture value and starts carrying real transport failures. Distinct from gunbc.host_disk_reclaim on purpose and stated in the module: that is a cadence over a host answering "healthy over weeks" and gates nothing; this is a barrier between two tenants, once per attempt, and is the only one that gates reuse. Folding them would make a slow disk-space job into a correctness dependency for every job start. Local parse gate: 0 diagnostics, rc=0, past reconcile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The fleet's hardware reconcile had verdicts and no reader: render expectation-versus-observation, and keep "never read" out of "confirmed" The operator asked for live fleet info on the daily workspace. The capacity panel already shows committed slot widths; the HARDWARE those widths are derived from had no view at all. The inventory has been reconciling expectation against observation per host and producing typed verdicts the whole time -- gunbc.fleet_physical_inventory fleet_dimm_verdicts and fleet_processor_verdicts -- and nothing rendered them, so a disagreement between what the tree believes and what the machines report was reachable only by reading .dag source. THE COST OF THAT WAS ALREADY PAID. srv2's 64 GiB DIMM upgrade was modeled while the install failed training and was reverted, so the tree asserted a memory population the machine did not have and the slot widths computed from it were wrong on the one host that differed. The verdict existed and said so. Nobody could see it. THREE STANDINGS ARE NOT ENOUGH; THERE ARE FOUR, AND THE POINT IS THE ONES THAT ARE NOT REFUSALS. Confirmed and Refused are the obvious pair. UNREAD says no reading was taken, so there is nothing to disagree with. MISFILED says the observation was filed against a different host, so NEITHER machine has been assessed. Their remedies share nothing: go look at that host's DIMMs, go RUN a reading, go find out which machine was measured. Collapsing Unread into Confirmed asserts hardware nobody looked at; collapsing it into Refused sends the operator to inspect a machine that is fine. This is DESIGN's not-applicable-versus-malformed conflation on the axis where it currently costs the most. MEASURED, live, on the real fleet: 4 of 4 memory confirmed · 0 of 4 processor confirmed srv1..srv4 memory confirmed 8 sticks as expected srv1..srv4 processor unread no per-host processor reading exists... EVERY PROCESSOR ROW IN THE FLEET IS UNREAD. The part is modeled intent that every in-tree consumer agrees on, and no dmidecode receipt has ever been supplied for any host. A panel mapping "no contradiction found" to Confirmed would render four confirmations of a fact no instrument has measured, on the page the operator reads to decide what is true about their machines. THE DISCRIMINATING RED, measured rather than asserted. Installing exactly that collapse (ProcessorModelUnconfirmed => HardwareConfirmed) in a copy of the tree: FALSE every_processor_row_is_unread_and_none_is_confirmed true the_memory_axis_is_confirmed_on_every_host true the_four_verdict_shapes_map_to_four_distinct_standings true the_four_standings_do_not_share_a_wire_word true a_refusal_detail_carries_the_count_and_the_discrepancy_tally FALSE the_summary_reports_each_axis_separately_and_never_one_fraction Four of six pass the collapsed mapping. All eight witnesses return true on this branch. THE SUMMARY IS PER AXIS AND NEVER ONE FRACTION. Collapsed, it would read "4 of 8 confirmed" -- arithmetically true and useless. Memory is fully read; the processor axis has never been measured once. A solved problem beside an unstarted one, and averaging them hides both. Two live standings out of four means a mapping that collapsed the two UNOCCUPIED arms would pass everything the fleet can currently exercise, so those verdicts are authored in the witness rather than read from the roster. Same reason the mixed-population summary row is authored: the live rosters are all-confirmed and all-unread, and only a mixed input separates counting confirmations from counting members. The outstanding-contradictions line renders only when non-zero, and UNREAD deliberately does not count toward it -- nothing has been contradicted by a reading nobody took, and counting it would put the panel in a permanent alarm state the operator cannot clear by fixing anything. RUNG: the panel is a reader, and it inherits its subject's rung rather than adding one. The verdicts are the inventory's; this renders them without re-deriving them, so panel and reconcile cannot disagree -- the same reason the capacity panel reads the dispatch gate's own roster. Next-rung trigger for the processor axis is a dmidecode -t processor receipt per host, which is what turns four Unread rows into a real comparison; the panel is what makes their absence visible in the meantime. Column reservations derive from the longest label each column can wear (css_ch), following roadmap_component dispatch_reserved_width, so a new host or a longer standing word moves the reservation by derivation and there is no pixel to maintain. Local parse gate: 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Style the panel on the existing refused-state vocabulary, and re-pin the CSS digest by execution Two things the panel commit owed, and one real defect that only the whole-page consumer could find. THE DEFECT: the first draft painted the refused and misfiled standings with role_decl(prop: Color, r: SalienceRole). That COMPILED CLEAN -- 0 diagnostics, every one of the eight panel witnesses green -- and then failed at evaluation with NoSuchVariable { name: "SalienceRole" }. SalienceRole is a TYPE in gunbc.design.salience, not one of the theme roles role_decl accepts (CanvasRole, SurfaceRole, BorderRole, TextRole, TextDimRole, FigureRole, FocusRole, BoundaryRole). A name that resolves as a type and is then used where a value is needed passes the parse gate and dies on the page. Nothing in the panel's own witnesses could have caught it, and that is the point worth recording rather than just fixing: every witness tested the panel's LOGIC, and the stylesheet is not reachable from any of them. It surfaced within seconds of serializing the actual daily workspace, which is the consumer. A green witness file is not evidence the page renders. THE FIX is not a new role. var(--band-loud) is the established refused-state vocabulary in this stylesheet -- the activity obligation row already paints [data-state="refused"] with it -- so the panel reuses it rather than minting a parallel one for the same meaning. Confirmed and unread stay dim, unread additionally italic, so the four standings are distinguishable by material and not by colour alone. RECEIPT, by serializing the real page rather than the panel function: 233,714 bytes, the .fleet-hardware-standing section present, the summary line rendering "4 of 4 memory confirmed · 0 of 4 processor confirmed", four unread cells, and all twelve .hardware-* rules emitted. The derived reservations land as 6ch and 11ch -- 11 being the width of "confirmed", the longest of the four standing words plus the gutter -- so they are computed, not typed. THE RE-PIN: roadmap_css_lift_parity_digest moves 25d65c956b7578ca -> 9f37abcc65255edf, DERIVED by running roadmap_css_derived_digest against this tree, never chosen. The accompanying note states what moved and why. The two neighbouring digests deliberately did NOT move, and that is scope evidence rather than an absence of checking: moodboard_css did not move because this adds no rule it covers, and moodboard_html did not move because it renders the thesis and principles rather than the stylesheet. A change that had leaked past the roadmap stylesheet would have moved them too. Both re-run green here alongside the re-pinned one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The slot-total witness assumed two symmetric pairs; name all four hosts 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 13db52a25d 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 * A refused serialization answered as an empty page, greening every negative 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 * Nothing prevents the allocator from consuming the thing that allocates: three measurements and a ruling Capacity has classes. Customer execution -- CI, agent tasks, batch -- is offerable. Control plane -- scheduler, reconciliation, admission, receipts -- is not: if control capacity appears as available vCPU, available RAM, or a cheap slot, the allocator can schedule customer work onto the resources that run the allocator. Nothing in the fabric supply model expresses that distinction, so nothing refuses it. THREE MEASUREMENTS, because any one alone is answerable and only together are they a finding. 1. NO CAPACITY CLASS ANYWHERE. Searched ControlPlane / control_plane / CapacityClass / capacity_class / Customer / customer across product.fabric.supply, identity, work and gunbc.dispatch_selection. One hit: the word "customer" inside a prose comment about displacement. No type, field or arm names the distinction. The hit is CARRIED rather than rounded to zero -- "no hits" is the claim a re-run falsifies, "one hit, in prose, here is why it does not count" survives the re-run. 2. THE PLAUSIBLE CARRIER CANNOT HOLD IT AS A FACT. The only field shaped to carry it is TrustDomainRef, a NonEmptyStr where brand. A branded string carries the class only as a magic value agreed between producer and consumer -- convention standing where necessity was available. That is WORSE than the current absence, because absence is visible and a convention reads as coverage. 3. THE PROTECTION HAS NO REACH ACROSS THE SEAM, and this is the one that changes the picture. gunbc.fabric_control_plane_charge is real and host-parametric: on the owned fleet a control-plane resident is SUBTRACTED from what a host can commit, which is why control capacity never becomes offerable there. Its importers are ci_runner_placement, fleet_host_budget, itself and one witness -- all fleet-side. Zero references to it or to fleet_host_budget anywhere under product/. So the protection does not WEAKEN at the external-supplier seam. It was never present on that side of it. Reading "control-plane capacity is charged" as a property of the fabric is authority substitution: the fact lives in one carrier, the operation is governed by another, and no carrier claims the arrow between them. THE RULING, from the product-direction lane: THE CLASS GOES ON THE WORK, NOT THE OFFER. An offer is a thing a SUPPLIER SAYS ABOUT CAPACITY. Putting the class there asks the party with the least knowledge and the most incentive to say yes to make the safety assertion, a supplier that omits the field is admitted, and the claim is unfalsifiable from our side. We originate the demand, so we hold the fact. Both refused remedies are KEPT with their reasons rather than deleted, and the witness asserts their reasons are distinct: the offer arm is refused on an AUTHORITY question, the trust_domain arm on a REPRESENTATION one. A shared "not ruled in" would lose exactly the distinction that stops someone arguing the second is fine once the first is addressed -- and the trust_domain shortcut is cheap, looks like modeling, and is the one a later reader will reach for. THE MEASUREMENT BASE IS NAMED. The Offer field census was taken on gunbc#8981's head, the widest that record has ever been. Measuring the narrower record on main and reporting "no capacity class" would have been true of a smaller surface and invited the reply that the field landed since. RUNG: this class sits BELOW mitigatable -- there is no failure to contain, the invalid state is simply representable and unremarked. It is not on the ladder. Next-rung trigger: the ruled remedy landing on Shape, at which point it becomes a matching refusal and can be measured as one. Nothing here is consumed by admission and the module says so; this row keeps the gap countable rather than rediscovered. ONE THING I MEASURED THAT A READER WOULD OTHERWISE GET WRONG. The eight DeclarationRefs here look like the ones the cited-symbol gate enforces. They are not: a fabricated decl_name leaves the gate green at checked=390, unchanged, before and after this module gained an importing witness. That is a DECLARED scope rather than a defect -- the lens's law enrolls doc-graph binds, "structural, not corpus text scan", and its dissolve-on already names widening to every carrier. Recorded in the module because a citation that looks enforced and is not is worse than a plain string. Five witnesses green; local parse gate 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The Spark slot assignment was decided the same day this note said it was missing: a rotted REASON, not a rotted line fleet_intent_network refused to author the srv5/srv6 endpoints because "the operator supplied the router table but never stated the assignment", so choosing an address would be a 50/50 guess and therefore the fabricated-plausible-output failure DESIGN §5 forbids. THE DECISION EXISTS AND IS ONE IMPORT AWAY. gunbc.spark.dgx_procurement `dgx_spark_router_binding_operator_allocation` records srv5 taking spark-a3ee and srv6 taking spark-3bd5, decided 2026-08-07 under an explicit operator delegation, on an ascending-address-order basis kept precisely so the tie-break is attributable rather than looking like a measurement. `srv5_router_binding` and `srv6_router_binding` are both SlotAssigned carrying it. Both notes are dated 2026-08-07 -- the stated blocker was resolved the day it was written. THE DOCTRINE WAS INVOKED AGAINST THE WRONG TARGET, which is why this is a correction rather than a refresh. §5 forbids fabricating an OBSERVATION. An attributable ALLOCATION with a decider, a date and a basis is not one -- it is exactly what the operator delegated. So the module was refusing on the grounds that a decision had not been made while the decision sat one import away. THIS IS THE STALE-CITATION CLASS IN ITS EXPENSIVE FORM: a rotted REASON rather than a rotted line number, and unlike a stale line it propagates by being believed. A lane read it, concluded Spark work was blocked on an operator fact, and reported that upward before anyone opened the procurement carrier. WHAT THIS DELIBERATELY DOES NOT DO: no endpoint row is added, and `endpoints` / gunbc.fleet_intent's ComputeHost list are unchanged. In this module that membership IS enrollment -- the module says so structurally, which is a good decision, so that naming an identity cannot be mistaken for enrolling it. Authoring the rows would enroll the units, not describe them, and there is nothing to enroll into while they are unplugged and the converge timer is retired. A PROJECTED ENDPOINT ALSO CANNOT LIVE HERE, and that is structural. gunbc.spark.dgx_procurement imports THIS module for operator_host_srv5/srv6, so the reverse import is refused by the import graph's one law. Measured, not inferred -- I added the import and compiled: circular dependency detected: gunbc.fleet_intent_network -> gunbc.spark.dgx_procurement Recorded in the note so the next person starts from the constraint rather than the idea. ONE METHOD NOTE WORTH MORE THAN THIS DIFF. My FIRST probe of that cycle reported ZERO diagnostics and I nearly recorded "no cycle". The trivial parse-gate entry never imports this module, so the cycle was never on the resolve path; it appeared only once the probe entry actually reached the module I had changed. Stated generally: A CLEAN COMPILE IS NOT EVIDENCE OF NO CYCLE UNLESS THE ENTRY REACHES THE MODULE YOU CHANGED. That is an absence produced by machinery that never performed the measurement, rendered identically to a real zero -- the same class as a green gate whose census never reached your file. The citation is symbolic, module and symbol, no line number (§3). Local parse gate with an entry that DOES reach this module: 0 blocking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * The axis column reserved the widest HOSTNAME for words that are not hostnames (review 55065) `.hardware-axis` took `hardware_subject_reserved_width()` — copied from the column beside it — so a column carrying "memory" and "processor" was reserved to the width of the longest HOST LABEL. Two unrelated populations that happen to be adjacent, and the derivation read the wrong one. Emitted 6ch for a column whose longest word is nine characters. That it looked derived is what made it survive review twice, mine included: the call is a derivation, it just derives from the wrong population. A hardcoded `11ch` would have been more obviously wrong. FIXED BY GIVING THE AXIS WORDS AN AUTHORITY. `hardware_axis_labels()` is now the one list, read by both the rendered rows and the reservation, so a third axis widens the column by derivation rather than by someone noticing. Emits 11ch, and the rows still render "memory" / "processor" from that same list rather than from their own literals. THE WITNESS ASSERTS THE DISCRIMINATING FACT, NOT THE TAUTOLOGY. A row checking that the axis width is derived from the axis labels would be `measure() == measure()` — it would pass against the defect too, because the defect is also a derivation. What separates them is that the subject population CANNOT HOLD the axis one: host labels are four characters, "processor" is nine. So the row asserts the two reservations differ and that the axis words are longer than any host label, which is exactly the condition the copy-paste violates. Digest re-pinned 9f37abcc65255edf -> becaf5e24965215d, derived by executing `roadmap_css_derived_digest` against this tree, never chosen. `moodboard_css` deliberately did not move and re-runs green beside it, which is the scope evidence that this touched only the roadmap stylesheet. Local parse gate: 0 diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Consolidate eight approved fabric/fleet PRs, and repair the two breaks the merge made silently Merges #8955 #8967 #8981 #8997 #9000 #9005 #9008 #9013 into one branch. Three resolutions carried real decisions, and two of them were invisible to git: - product.fabric.supply offer_fungibility_for: #8981 rewrote the body while main renamed Offer<P> to SupplierOffer<P>. Kept the new body on the current type name; unioned the import list. - gunbc.roadmap_style: #9005 and #8967 each added a panel in the same region. The first union interleaved the two rule sets into a file that parsed as garbage (expected LParen, found Ident) with NO conflict markers present. Redone as a real 3-way and checked at the block boundary. - roadmap_css_lift_parity_digest: both branches re-pinned it against a stylesheet holding only their own rules, so NEITHER value describes the merged sheet. Taking a side would have pinned a digest no emission produces. Re-derived by executing roadmap_css_derived_digest against this tree: e1434344ca3877e4. And one break git merged cleanly into a third file: #8960's t_offer_with_quantum predates #8981's required Shape.envelope and SupplierOffer.isolation, so the literal lost its type. Both branches were green alone; only the merged tree refuses. Repaired in the same style as t_offer beside it. Verified on this branch: 0 blocking parse errors (the 52 source-annotation diagnostics are pre-existing on main, same file, same count, measured with the same probe against a clean checkout); every witness in every changed test file passes. * The last construction site the merge stranded: ubicloud's offer, and the diagnosis that outlived its deficit CI's floor found what my entry-scoped probe could not: dag/product/supplier/ubicloud.dag is a Shape/SupplierOffer construction site from main's #8960 that predates #8981 making `envelope` and `isolation` required. Same class as t_offer_with_quantum, different file, and the floor resolves 3866 modules where my probe resolved one import closure. The fill is not mechanical, and neither field takes a placeholder: envelope: the catalog publishes vcpus and memory_bytes, and this binding is the REASON #8981 added the field -- product.fabric.work records it, citing product.supplier.ubicloud by name. So memory is now EXPRESSED via cpu_memory_envelope, and MemoryNotExpressibleInShape is DELETED from UnexpressedSupplyFact rather than carried beside it. It would now be a false statement about the model. A diagnosis that outlives the deficit it diagnoses is worse than absent: it gets cited as a known gap while the gap is closed. Disk stays, because the storage axis is still unpopulated from this catalog and that fact is still true; the asymmetry is now the type's whole content. isolation: nobody has measured what a Ubicloud runner provides. The catalog carries vcpus, memory and disk and says nothing about kernel tenancy, namespacing or egress, so naming shared_kernel_sandbox_profile() would invent a vendor guarantee from a price list -- and worse than a wrong note, a broker would ROUTE work to it. The empty profile is fail-closed by construction: unsatisfied_isolation_guarantees filters the REQUIRED set against it, so every guarantee any work asks for comes back missing and the offer refuses BY NAME, while work requiring nothing still matches. The empty list alone would conflate "provides none" with "nobody looked", so IsolationNotObserved is added beside it as a stated coverage obligation. Censused every fabric Shape and SupplierOffer literal in the tree by hand afterwards rather than trusting one entry closure again: ubicloud was the last one. NOT FIXED HERE AND NOT MINE: the same floor run also refuses runner_slot_provision.dag:240 on a sole_constructor ArgvCommand. That site is untouched by this branch, is present on main, and main's own run 32646482842 is red on it. #9031 carries that repair. * gib_label divides by the authority, not by 1073741824 review 55104, advisory. The literal was a correct number and a second representation of one std.measure already owns: gibibyte_scale_factor_bytes builds it from extdeps.units.iec_80000_13 iec_kibi_factor. Display-only is not an exemption -- that literal is what a GB/GiB confusion is made of, and the authority is what already settled which one this is. All 10 fleet_capacity_panel witnesses pass, including the whole-page serialization row. --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Style the panel on the existing refused-state vocabulary, and re-pin the CSS digest by execution
Two things the panel commit owed, and one real defect that only the
whole-page consumer could find.
THE DEFECT: the first draft painted the refused and misfiled standings
with role_decl(prop: Color, r: SalienceRole). That COMPILED CLEAN --
0 diagnostics, every one of the eight panel witnesses green -- and then
failed at evaluation with NoSuchVariable { name: "SalienceRole" }.
SalienceRole is a TYPE in gunbc.design.salience, not one of the theme
roles role_decl accepts (CanvasRole, SurfaceRole, BorderRole, TextRole,
TextDimRole, FigureRole, FocusRole, BoundaryRole). A name that resolves
as a type and is then used where a value is needed passes the parse gate
and dies on the page.
Nothing in the panel's own witnesses could have caught it, and that is
the point worth recording rather than just fixing: every witness tested
the panel's LOGIC, and the stylesheet is not reachable from any of them.
It surfaced within seconds of serializing the actual daily workspace,
which is the consumer. A green witness file is not evidence the page
renders.
THE FIX is not a new role. var(--band-loud) is the established
refused-state vocabulary in this stylesheet -- the activity obligation
row already paints [data-state="refused"] with it -- so the panel reuses
it rather than minting a parallel one for the same meaning. Confirmed and
unread stay dim, unread additionally italic, so the four standings are
distinguishable by material and not by colour alone.
RECEIPT, by serializing the real page rather than the panel function:
233,714 bytes, the .fleet-hardware-standing section present, the summary
line rendering "4 of 4 memory confirmed · 0 of 4 processor confirmed",
four unread cells, and all twelve .hardware-* rules emitted. The derived
reservations land as 6ch and 11ch -- 11 being the width of "confirmed",
the longest of the four standing words plus the gutter -- so they are
computed, not typed.
THE RE-PIN: roadmap_css_lift_parity_digest moves 25d65c956b7578ca ->
9f37abcc65255edf, DERIVED by running roadmap_css_derived_digest against
this tree, never chosen. The accompanying note states what moved and why.
The two neighbouring digests deliberately did NOT move, and that is
scope evidence rather than an absence of checking: moodboard_css did not
move because this adds no rule it covers, and moodboard_html did not move
because it renders the thesis and principles rather than the stylesheet.
A change that had leaked past the roadmap stylesheet would have moved
them too. Both re-run green here alongside the re-pinned one.
The fleet's hardware reconcile had verdicts and no reader: render expectation-versus-observation, and keep "never read" out of "confirmed"
The operator asked for live fleet info on the daily workspace. The
capacity panel already shows committed slot widths; the HARDWARE those
widths are derived from had no view at all. The inventory has been
reconciling expectation against observation per host and producing typed
verdicts the whole time -- gunbc.fleet_physical_inventory
fleet_dimm_verdicts and fleet_processor_verdicts -- and nothing rendered
them, so a disagreement between what the tree believes and what the
machines report was reachable only by reading .dag source.
THE COST OF THAT WAS ALREADY PAID. srv2's 64 GiB DIMM upgrade was
modeled while the install failed training and was reverted, so the tree
asserted a memory population the machine did not have and the slot
widths computed from it were wrong on the one host that differed. The
verdict existed and said so. Nobody could see it.
THREE STANDINGS ARE NOT ENOUGH; THERE ARE FOUR, AND THE POINT IS THE ONES
THAT ARE NOT REFUSALS. Confirmed and Refused are the obvious pair. UNREAD
says no reading was taken, so there is nothing to disagree with. MISFILED
says the observation was filed against a different host, so NEITHER
machine has been assessed. Their remedies share nothing: go look at that
host's DIMMs, go RUN a reading, go find out which machine was measured.
Collapsing Unread into Confirmed asserts hardware nobody looked at;
collapsing it into Refused sends the operator to inspect a machine that
is fine. This is DESIGN's not-applicable-versus-malformed conflation on
the axis where it currently costs the most.
MEASURED, live, on the real fleet:
4 of 4 memory confirmed · 0 of 4 processor confirmed
srv1..srv4 memory confirmed 8 sticks as expected
srv1..srv4 processor unread no per-host processor reading exists...
EVERY PROCESSOR ROW IN THE FLEET IS UNREAD. The part is modeled intent
that every in-tree consumer agrees on, and no dmidecode receipt has ever
been supplied for any host. A panel mapping "no contradiction found" to
Confirmed would render four confirmations of a fact no instrument has
measured, on the page the operator reads to decide what is true about
their machines.
THE DISCRIMINATING RED, measured rather than asserted. Installing exactly
that collapse (ProcessorModelUnconfirmed => HardwareConfirmed) in a copy
of the tree:
FALSE every_processor_row_is_unread_and_none_is_confirmed
true the_memory_axis_is_confirmed_on_every_host
true the_four_verdict_shapes_map_to_four_distinct_standings
true the_four_standings_do_not_share_a_wire_word
true a_refusal_detail_carries_the_count_and_the_discrepancy_tally
FALSE the_summary_reports_each_axis_separately_and_never_one_fraction
Four of six pass the collapsed mapping. All eight witnesses return true
on this branch.
THE SUMMARY IS PER AXIS AND NEVER ONE FRACTION. Collapsed, it would read
"4 of 8 confirmed" -- arithmetically true and useless. Memory is fully
read; the processor axis has never been measured once. A solved problem
beside an unstarted one, and averaging them hides both.
Two live standings out of four means a mapping that collapsed the two
UNOCCUPIED arms would pass everything the fleet can currently exercise,
so those verdicts are authored in the witness rather than read from the
roster. Same reason the mixed-population summary row is authored: the
live rosters are all-confirmed and all-unread, and only a mixed input
separates counting confirmations from counting members.
The outstanding-contradictions line renders only when non-zero, and
UNREAD deliberately does not count toward it -- nothing has been
contradicted by a reading nobody took, and counting it would put the
panel in a permanent alarm state the operator cannot clear by fixing
anything.
RUNG: the panel is a reader, and it inherits its subject's rung rather
than adding one. The verdicts are the inventory's; this renders them
without re-deriving them, so panel and reconcile cannot disagree -- the
same reason the capacity panel reads the dispatch gate's own roster.
Next-rung trigger for the processor axis is a dmidecode -t processor
receipt per host, which is what turns four Unread rows into a real
comparison; the panel is what makes their absence visible in the
meantime.
Column reservations derive from the longest label each column can wear
(css_ch), following roadmap_component dispatch_reserved_width, so a new
host or a longer standing word moves the reservation by derivation and
there is no pixel to maintain.
Local parse gate: 0 diagnostics.
CI
The eight
required-floor: FAILrows on this PR are main's, not thisbranch's. Main run 32621117917 at 13db52a fails exactly the same
eight; this PR adds none. They are owned by #8987 (srv2 DIMM homonym),
#8992 and #8998 (width assertions stranded by the CPU-axis change).
🤖 Generated with Claude Code
https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf