Repository navigation
Consolidate the fabric cell family onto main: one member family, six findings folded in - #9122
Conversation
…threaded through the plan/apply spine The one fabric execution slot in the fleet -- srv3-06 -- is declared in gunbc.runner_slot_allocation and could not be converged: gunbc.runner_lifecycle refuses it, because a fabric cell is not a GitHub runner with pieces removed, and nothing else answered for it. This lands the family that does, and threads it through fleet converge so it is a fact the spine carries rather than a model nothing consumes. TWO HALVES, ONE PR, BECAUSE EITHER ALONE IS ANOTHER UNCONSUMED MODEL. A family with no spine integration is a model nothing reads; a spine population with no family has nothing to put in it. WHAT THE MEMBER GRAIN IS, AND WHY NOT A CELL-WIDE DIGEST. Three resource addresses -- cell root, attempt-state root, resource boundary -- following the Spark serving family rather than inventing a second shape beside it. One digest over the whole cell produces a wrong REMEDY, not merely a coarse report: a drifted cgroup and a missing directory would hash differently and both select "provision the cell", so a host needing only its limits reapplied pays for a full installation. The boundary's desired digest CONSUMES gunbc_runner_slot_desired rather than re-spelling it, so a change to the fleet's per-slot row surfaces as MemberChanged at exactly one address. runner_lifecycle KEEPS REFUSING, AND THAT IS DELIBERATE. Its fabric arm is renamed FabricSlotHasNoGitHubAddSequence -- the old name asserted that fabric provisioning was unmodeled, which this change falsifies -- but it stays a refusal. A sibling family here PLUS a success arm there would be two provisioning authorities for one cell: the single-authority violation arrived at by addition rather than by nicknaming, which is the harder form to see because neither half looks like a duplicate alone. THE OBSERVED SIDE IS A TYPED POPULATION, NOT A LIST, AND THAT IS THE LOAD-BEARING CHOICE. The sibling families take List<Member>, so a caller with no observer passes [] and reconcile reads it as "the host has none of these" and answers with a full install plan. For a family whose resources are a cgroup and two directories that is the empty-observation narrow with real consequences: a host nobody read is planned as a host with nothing on it. FabricCellPopulation makes unread and empty different values; the refused arm contributes its OWN baseline row, so the two hash differently and a plan approved under one cannot admit under the other. No observer exists yet, so the CLI passes the typed refusal -- which stops the family at the plan boundary, renders a line the reviewer sees, and is counted beside the other families' refusal counts. A degradation nobody counts is one nobody ranks for fixing. CONTRADICTION REFUSES RATHER THAN RESOLVING BY ORDER. Every observation of an address is folded and any doubt dominates: refusal beats contradiction beats absence beats presence. A first-match fold would let a favourable observation win by arriving first, and the witness fixture authors the favourable observation FIRST precisely so that implementation would go green on it. WHAT THIS DELIBERATELY DOES NOT CLAIM. "Substrate provisioned" means the root, the attempt root and the boundary exist as declared. It does NOT mean an executor is ready: there is no fabric agent in this tree, no executable entry, and nothing that could observe one running. An earlier revision of this module enumerated an agent service identity, a control-plane binding, materialization endpoints and a supervised-dormant agent as provisioning steps; naming them would have made "provisioned" mean "an executor is ready" while the only establishable facts are a directory, a directory and a cgroup -- the rung inflation DESIGN 4b calls worse than sitting low, because an inflated claim never ranks for climbing. They are removed with a named trigger, not abbreviated. No SupplierOffer, no Demand, no Attempt, no ExecutionGrant, no process start, and the apply shell emits the resolved membership as COMMENTS rather than host effects -- derived from the same typed resolution the reviewer read, so the realization replaces a projection that already had to be correct. GREEN BY EXECUTION, 15 of 15 new rows, this tree: 11 fabric cell model rows (dag/test/claim/fabric_cell_converge_witness_test.dag) 4 spine acceptance controls (dag/test/claim/fleet_converge_plan_witness_test.dag) The load-bearing one is witness_fabric_cell_population_moves_the_member_set_fingerprint: a converged population and an empty one produce DIFFERENT member-set fingerprints, and the same population produces the same one. It is also the mutation control for the threading -- delete the fabric rows from observed_baseline_text and it goes red, because the two populations then differ in nothing the fingerprint reads. Compile: 0 blocking diagnostics; the only advisories naming these files are the standard where-refinement-unenforced class every `as NonEmptyStr` cast produces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e narrow, the unrealizable plan, the refusal baseline review 55284 and a product-direction review found five real problems in the first head. None were style; two were live fail-opens, one was latent on the next change, one falsified an extension claim this module makes about itself, and one was a fingerprint collision. Each is fixed where it originates rather than patched at the call site, and each carries a discriminating row. 1. THE FOLD TOOK THE FIRST OBSERVED MEMBER AND CALLED IT AGREEMENT. first_observed_member returned the first AddressObserved and never compared the rest, so two attesters reporting different digests for one address resolved to whichever arrived first -- sitting directly underneath an annotation asserting that presence requires every observation to agree. One ordering yielded Provisioned and the reverse yielded Unprovisioned on the same readback. The witness did not catch it because its contradiction fixture paired an observation with an ABSENCE, which was always caught; the conflicting-POSITIVE case had no row, and it is the likelier readback of the two -- a stale reader and a fresh one both see the resource and disagree about its value. Agreement is now computed through a typed ObservedDigestAgreement where one disagreement collapses to Conflict permanently. 2. FOUR BOOLEAN PREDICATES IN THE AUTHORITY. disposition_is_present, disposition_blocks_verdict, disposition_member and fabric_cell_address_matches_desired each matched the whole disposition coproduct and answered a Bool or a list. That is the predicate-dissolution shape, and it is the rule I invoked defending gunbc#9069 -- where the predicate sits in a WITNESS and the authority carries none. These were authority-side, so the objection lands here. They are replaced by one descent, FabricCellAddressStanding, with three named outcomes; a new disposition arm now fails to compile in one place instead of silently inheriting whichever Bool three separate matches gave it. 3. AN UNOBSERVED ADDRESS COUNTED AS AN ABSENT ONE. AddressUnobserved mapped to the non-blocking standing, so a readback holding NO observations produced FabricCellPopulationObserved with an empty member list -- and reconcile reads an empty observed population as "the host has none of these" and answers with three ADDs. The typed population closed the empty-observation narrow at the caller and this reopened it one layer in. MissingOnHost and Unobserved are now on opposite sides: one is an observation that the resource is absent, which reconcile may act on; the other is the absence of an observation, which is not a fact about the host at all. THE VERDICT MOVED WITH IT and a witness that encoded the wrong answer is corrected: an empty readback reports ObservationRefused, not Unprovisioned. The distinction I was protecting -- our evidence versus the host refusing -- is real and survives in the DISPOSITION, where AddressUnobserved and AddressUnreadable stay separate arms with different remedies. It does not belong in the verdict, because both are the same answer to the question the verdict asks. Unprovisioned was a claim about a host nobody looked at. 4. A PLAN WITH WORK DUE AND NO REALIZER REPORTED ZERO REFUSALS. An observed-empty population produced three MemberAdded actions, three ADD lines, a zero refusal count, and an apply shell that emits them as comments: "three additions due, zero refusals, zero host effects, apply completes" -- work reported as due against a plan that will not do it. Latent only because the CLI has no observer; landing one would have turned it live on the first honest empty read. The family has a third arm, FabricCellFamilyRealizationUnavailable, carrying the actions it refused to realize. Each renders REFUSED-REALIZATION and counts as one refusal, because the deficit is per unit of work owed. It is an arm rather than a check, so the converged-looking-but-unrealizable state has no spelling. 5. THE REFUSAL BASELINE WAS ONE ROW. A single fabric-cell-observation-refused line made two hosts blocked for different reasons at different addresses hash identically, so a plan approved while the cgroup was unreadable would admit later while the attempt root was unreadable instead. Each blocked disposition now contributes a row naming its slot-qualified address and cause. Same class as finding 3, one layer out: an observation that cannot express what it saw, rendered as a verdict. ALSO, FROM THE SAME REVIEW: the population is host-scoped, matching the host-scoped desired side, instead of carrying one CellId that its observed arm ignored and its unobserved constructor filled with a synthetic "fabric-cell-population" identity naming nothing. Dispositions carry a slot-qualified FabricCellAddressRef, so "resource-boundary unreadable" can say WHICH cell -- which makes this module's extension claim true rather than aspirational: a second fabric identity now reaches the blocked path named. VERDICT ON THE TYPED POPULATION, since it was the one thing I flagged as a deliberate deviation: keep it, do not match the sibling List<Member> signatures. A bare list cannot distinguish "the observer proved there are no members" from "no usable observation was produced", and spark and slots should move toward this shape rather than pulling fabric back into their ambiguity. GREEN BY EXECUTION, 13 of 13 model rows on the settled tree, including the three new discriminators: conflicting-positive observations contradict (the favourable reading authored FIRST, so a first-match implementation goes green and only a comparing fold goes red); agreeing duplicates stay present (without which the fix could be "any second observation contradicts", turning a redundant probe into an outage); empty readback refuses. Compile: 0 blocking diagnostics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…th it
review 55308 found fabric_cell_ids defined at 549 and 591 and
fabric_cell_population_unobserved at 580 and 608 -- an append that never deleted
what it replaced. The trailing block is removed, one definition each remains,
and the observer-refusal cause is back to a single authority.
THE REVIEW'S ONE UNVERIFIED INFERENCE IS THE INTERESTING PART, AND IT IS WRONG
IN THE DIRECTION THAT MATTERS. It says "this will fail to parse/typecheck". It
does not. Measured on a standalone two-function module through our own binary:
fn which_one() -> Bool { false } <- first
fn which_one() -> Bool { true } <- second
=> PASS (returned true: the SECOND definition bound)
control, order swapped:
fn which_one() -> Bool { true } <- first
fn which_one() -> Bool { false } <- second
=> FAIL (returned false: the SECOND definition bound)
The result flips with the order, so it is not a constant and not a harness
artifact. Silent last-definition-wins for module-local functions: no refusal, no
diagnostic, no advisory.
WHICH COPY WAS LIVE, AND IT WAS THE BAD ONE. Last-wins means the second
definition bound -- the copy inlining the cause string, not the one consuming the
fabric_cell_no_observer_cause data row. The parallel-representation violation
riding on top of the redefinition was executing rather than latent, and every
caller was getting the copy this module's own annotations argue against.
WHY THE VERIFICATION MISSED IT, SAID PLAINLY. The compile was not skipped and did
not predate the duplicating edit: it ran on exactly that tree, after every edit,
and reported zero blocking errors with 880 advisories, and grepping its log for
duplicate/redefin/already-defined/shadow returns nothing. So "compile: 0 blocking
diagnostics" was a true quotation and worthless as evidence -- the compiler's
silence was cited as support for a property the compiler does not check. That is
the failure this very module's annotations describe: total at the level examined,
blind one level below. The check that would have caught it is the one that found
it, a grep for two definitions of one name, and it is now what this commit's
verification rests on rather than the compile.
CLASS, NOT FILED HERE ON PURPOSE: this is the shape DESIGN records for imports --
a census-ambiguous name resolving by silent last-import-wins instead of refusing
-- one layer down at module-local function definitions, found in production
rather than in a fixture, and below the ordinary compiler floor DESIGN names
first. The gap analysis entry deserves its own change with the fixture enrolled
as evidence, not a paragraph smuggled into a fabric cut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…that let a member disagree with its own slot The runner_lifecycle annotation named gunbc.fabric_cell_converge fabric_cell_provision_sequence_for as the fabric slot's provisioning sequence. That symbol does not exist: the same PR deleted the step vocabulary and replaced it with a membership model, so the annotation described the shape the work started from. Its next-rung trigger was wrong the same way — it named a desired population that the host-exact fold already satisfies, which would have marked the row done while both real gaps stayed open. FabricCellMember carried host beside slot, and RunnerSlotIdentity already contains the host, so member.host = srv4 beside member.slot.host = srv3 was representable. Derived rather than stored: unrepresentable, not validated. observation_targets compared two rendered instance names; the identity authority is runner_slot_identity_equal.
…answer for a host The population was host-scoped in name and produced by a constructor that evaluated ONE slot's three addresses. Correct only while srv3 declares exactly one fabric cell; the moment it declares two, the second cell's resources are unobserved and a host-scoped population reports them ABSENT, so reconcile plans them against a host nobody read. Sealing that constructor would have made the single-cell assumption the sanctioned path. So: FabricCellHostReadback carries one entry per cell read, and completeness is decided against fabric_execution_slot_identities rather than against what the caller supplied. Only then is the member list sealed behind a sole_constructor carrier, so observed-empty cannot be asserted by literal. Two identities that could disagree are gone with it: FabricCellReadback carried a CellId beside the slot every interpreter took separately, and FabricCellMember carried the ownership that licenses its own teardown.
…ort I wrote to establish it The stability half of the fingerprint row compared two identically-ordered constructions — determinism, not permutation-stability. I added a canonicalizing sort_by over the fabric baseline rows to close it, and the mutation receipt refuted the fix: with the sort deleted the reversed observation still hashed identically. Member order never depended on observation order. fabric_cell_population folds over the authority's expected subjects and looks each one up, so the member list is in authority order for any permutation. The sort was validation standing where construction already held, and would have been cited afterwards as the reason the fingerprint is stable. Sort deleted; the row stays and guards the real property — it goes red if the member list is ever derived from the observations, which is the shape an observer author would naturally reach for. Receipt: authority-ordered dispositions PASS, observation-ordered dispositions FAIL.
…our times Both findings from codex review 55419, verified against the code. The observed arm discarded the population's host and reconciled against the separately supplied plan host, so a valid srv4 population joined to an srv3 plan read as srv3 evidence: srv3's resources absent, srv4's extra, a full install plus a full teardown against a host nobody read. Now a typed FabricCellFamilyObservationHostMismatch, rendered and counted. Typed rather than silently corrected because both readings are wrong and the caller must learn which. any_refused, any_absent, first_refusal_cause and observed_digest_agreement each walked the same observation list and projected the coproduct to a Bool or a String. One fold to FabricCellAddressEvidence, one descent to the disposition. Refusal and conflict absorb, so the order-freedom is the join rather than the arm sequence. The new row also surfaced a second collision the first finding hid: the observed arm's baseline rows omitted the host, so two observed-empty populations on different hosts hashed identically. Fixed in the same arm the refused half was already fixed in.
…venting a refusal where nothing was owed fabric_cell_population_unobserved folded the GLOBAL slot roster, so planning any host — srv4, or a host with no fabric cells at all — fabricated an srv3-06 unreadable-resource refusal and stopped the line over resources that host was never expected to carry. The CLI builds this population on every plan, so it was production, not latent. Two errors, and filtering alone would have left the second: the roster is now host-filtered through the same fabric_cell_expected_slots the observing fold uses, and a host expecting no cells converges observed-empty rather than refused. There is nothing unread about a host with nothing to read; refusing there invents an obligation. The two routes now agree by construction — the unobserved constructor and the observing fold answer the same thing for a host with no expected cells, because both derive the expected set from one authority. They disagreed before: the fold was already host-exact and this constructor was not.
…and stop a comment asserting the opposite of its code fabric_cell_readback_for_slot folded with if-matches-take-it, so two entries for one slot — a retried probe, two attesters, a re-read after a timeout — resolved to whichever came last and the other was discarded unexamined. That is the favourable-reading-by-position hazard the address-level evidence fold exists to eliminate, sitting one level above where that fold could see it: it only ever received the surviving readback. Merged rather than rejected, because the address level already decides this properly — two probes agreeing are Present, disagreeing are Contradicted, one refusing makes the address Unreadable. Rejecting duplicates would refuse the healthy redundant probe and decide at slot grain a question whose facts are per address. And the comment on fabric_cell_readback_without_observations still said an unread cell is Unprovisioned. It has been Refused since the empty-observation narrow was closed. Unprovisioned means plan the missing members; Refused means plan nothing until the host is read — so the sentence told a reader the module does what it was repaired not to do.
…-family-consolidated # Conflicts: # dag/test/claim/fleet_converge_plan_witness_test.dag
|
CI re-checked at Run 32770114118, same refusal as before: None of the four is in this diff, and re-verified against current main just now: Both carrier files are byte-identical to main. The refusal is a pure join over two hand-authored rosters that this branch does not touch, so it fires for every PR based on main and will keep firing here until the carriers change. #9133 carries the repair (retire the four stale Why I am not repairing it inside this PR, since that is the tempting move when a dashboard says "push a fix": the carriers are outside this lane, the disposition is already settled and authored elsewhere, and three separate lanes today repaired this same intersection inside their own unrelated PRs before it was caught. Two sessions independently editing one roster is how the collision was created in the first place. This branch touches neither file, so when #9133 lands it needs a re-run, not a rebase. The fabric work itself is verified on this tree: 16/16 model rows, 6/6 fabric spine rows, 0 blocking on both entry compiles, no duplicate definitions in any touched file. — sent from silent-bear-842 |
Supersedes #9081, #9105 and #9109 (all three now closed, each pointing here). This consolidates a three-deep stack after #9070 landed and put all three into CONFLICT at once.
This is not ten unreviewed commits. Each of the three prior PRs carried its own approval, and the fabric work has been through four rounds of adversarial review — six real findings, all folded in below.
git merge-base --is-ancestorconfirms all three heads are ancestors of this branch, so the history is preserved rather than rebuilt.What the consolidation changed: the union is exactly the three branches' content, plus (a) one conflict resolution in
dag/test/claim/fleet_converge_plan_witness_test.dagand (b) one commit that predates the consolidation and belongs to the fabric work. Nothing else was edited under cover of the merge.The conflict was additive on both sides — main added sudo-grant witnesses, this branch added the fabric section, at a shared insertion point, plus a one-line import union. I checked it was genuinely additive rather than two authors restating one fact: no duplicate definitions in any of the four touched files. That check exists because this compiler binds the second of two same-name definitions silently with no diagnostic (#9093, filed off this same work, with a confirmed production victim in this very file's history).
What this delivers
The srv3-06 compute-fabric execution cell converges as its own member family, threaded through the whole fleet plan/apply spine. Static substrate only — no Offers, Grants, containers or sanitation actuation. srv3-06 is still not convergeable, and
runner_lifecycle's annotation says so plainly.FabricCellHostReadbackis the observation subject, and completeness is decided againstfabric_execution_slot_identitiesfiltered to the host — not against what the caller supplied. An unread expected cell folds toAddressUnobserved→StandingBlocked→ refused.FabricCellObservedMembersissole_constructor, minted only by that fold, so "observed empty" cannot be asserted by a cross-module literal.fabric_cell_address_evidencereplacesany_refused/any_absent/first_refusal_cause/observed_digest_agreement. Refusal and conflict absorb, so order-freedom is the join rather than arm sequence.FabricCellFamilyObservationRefused,FabricCellFamilyRealizationUnavailable(a nonempty plan with no realizer is counted per unrealizable action, never rendered as clean ADDs), andFabricCellFamilyObservationHostMismatch.FabricCellReadback.cell(CellId derived from the slot),FabricCellMember.host(derived from the slot),FabricCellMember.ownership(derived from the closed namespace, so the observed side no longer authors the fact licensing its own teardown).Six findings folded in
fabric_cell_population_unobservedfolded the global slot roster, so planning any host fabricated an srv3-06 refusal. Live in production planning. Fixed both halves: host-filtered, and a host expecting no cells converges observed-empty rather than refused, since refusing there invents an obligation.Bool/String.runner_lifecyclenamedfabric_cell_provision_sequence_for, a symbol that never existed, with a next-rung trigger already satisfied by this PR's own fold.Two of my own fixes were refuted by their own mutation receipts and deleted rather than kept: a canonicalizing
sort_bythat turned out to be redundant (the fold was already authority-ordered), and an expected-red enrolment that could never execute.Evidence, measured on this tree
fabric/cell-family-consolidated@eb2a70c1a0, after merging current main:Run one row at a time on a settled tree — not cited from the branches this came from.
CI status
The
witnessesjob fails withRouteGapFreezeIntersection count=4, and it is not from this change. The four colliding identities (deploy_access_privilege_witness, three fromhost_effect_apply_witness) are absent from this diff, andgit diff origin/main HEAD -- src/v2/workflow/floor_route_gap.dag dag/gunbc/witness_deferral_freeze.dagis empty — both carriers are byte-identical to main. It is a pure join over two hand-authored rosters on main, so it fires for every PR based on it.#9133 carries the repair (retire the four
frozen_path_deferralsrows, keep the route-gap receipts) and is waiting on a runner. Root cause: #9049 and #9114 merged 1m43s apart, each having measured a clean join against a main that did not contain the other, and with no merge queue nothing evaluated the union until CI ran on the result. This branch touches neither carrier, so it will not conflict with #9133 — it just needs a re-run once that lands. Until then the witnesses red here is inherited, not this change.One correction worth recording, since it nearly went out as a claim: I first reported main as green on this check. It is not — it is unmeasured. I had read
conclusion=successoffgh run listwithout checking the workflow name, and those runs werefleet-convergeandfleet-desired. Everywitnessesrun on main is queued. Absence of a red main run is not evidence main is green; it is evidence nothing has finished.Still open
The N>1 fabric-cell path has no executable fixture, because production declares one cell. Reachable-but-unoccupied — a quiet guard, not a dead one. The indicated fix is to split a pure classification kernel (accepts a synthetic expected set, mints nothing) from the authority-bound constructor that alone mints the sealed population. That, and the observer, come next — branched from main, not stacked here.
— sent from silent-bear-842