Skip to content

Observe at host grain, then seal it: completeness decided by the authority, not by what the caller supplied - #9105

Closed
briansrls wants to merge 5 commits into
mainfrom
fabric/seal-observed-population
Closed

briansrls wants to merge 5 commits into
mainfrom
fabric/seal-observed-population

Conversation

@briansrls

@briansrls briansrls commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #9081 (branched from 8c4520bce9), so the diff shows that PR's commits until it merges. This PR is the last commit only.

What this closes, and one thing it deliberately does not do first

A side-chat review of #9081 flagged that FabricCellPopulationObserved is publicly constructible — the type names the distinction between "the host was read and holds nothing" and "no usable observation was produced" without controlling who may assert it. The spine witness in fact asserted observed-empty by raw literal, with no observation anywhere behind it.

The obvious fix is sole_constructor on the member list. That fix alone would have been wrong, and the same review caught why: the population was host-scoped in name, but produced by

fabric_cell_population(readback: FabricCellReadback, slot: RunnerSlotIdentity)

which evaluates one slot's three addresses and labels the result the observation for the whole host. That is correct only while srv3 declares exactly one fabric cell. The moment it declares two, the second cell's resources are unobserved, a host-scoped population reports them as absent, and reconcile plans them as additions against a host nobody read — the empty-observation narrow reintroduced at cell grain, underneath the very type that exists to prevent it. Sealing that constructor would have made the single-cell assumption the sanctioned construction path.

So the grain is fixed first, then sealed:

  • FabricCellHostReadback { host, cell_readbacks } is the observation subject, and fabric_cell_population takes it alone.
  • Completeness is decided against the authority, not the caller. The fold enumerates fabric_execution_slot_identities filtered to the host × fabric_cell_resource_addresses. A slot the caller did not read folds to an observation-less readback → every address AddressUnobserved → StandingBlocked → the refused arm.
  • FabricCellObservedMembers is sole_constructor, minted only by that fold. A cross-module record literal is refused at compile time.

The module states the wall's real scope rather than overclaiming it: sole_constructor confines who constructs, not what is true of the value (gap analysis item 1a). It is exactly right here because the fold is the module's own fixed law with no caller-supplied predicate — every member came from a StandingSatisfied, and blocked standings divert the whole population before the carrier is reached.

Two writable identity pairs deleted with it

  • FabricCellReadback.cell — a CellId carried beside the slot that every interpreting function took separately, with nothing comparing them. A readback branded srv4-06 could carry observations about srv3-06 and return a verdict naming srv4-06. The readback now carries slot, and CellId is derived.
  • FabricCellMember.ownership — reconcile consults fabric_cell_member_ownership when deciding whether an observed extra may be removed, so an observed member authored the fact licensing its own teardown. The namespace is closed (RunnerSlotIdentity × FabricCellResourceAddress, both authority-owned), so Owned is derived.

Evidence

14/14 model rows PASS, including the new one:

witness_fabric_cell_unread_expected_cell_refuses_despite_a_complete_foreign_readback   PASS

It supplies a complete, fully-converged readback for a slot that is not a declared fabric member of the host. The expected subject srv3-06 therefore has no readback, folds to unobserved, and the population must refuse. A fold trusting the supplied list would see one complete cell readback, find nothing blocked, and answer PopulationObserved with the real cell silently missing.

Mutation receipt — the row is discriminating, not decorative. With fabric_cell_host_standings temporarily folding over readback.cell_readbacks instead of the expected slots:

FAIL witness_fabric_cell_unread_expected_cell_refuses_despite_a_complete_foreign_readback

Restored, it passes. The other 13 rows pass in both trees, which is why this one had to be added rather than relying on them.

What this row does not cover, stated rather than implied: the two-declared-cells case, where one is read and the other is not. fabric_execution_slot_identities declares exactly one fabric slot today, so that input is not authorable without adding a second slot to the authority. The guard is reachable and currently unoccupied — a quiet guard, not a dead one — and this row exercises the same mechanism through the one input the authority does admit.

The spine witness's fabric_cells_observed_empty now builds a real readback of explicit AddressAbsent observations and runs the production fold, which also delivers the "three explicit absences → observed-empty → exactly three ADDs" control that review asked for. Read-and-empty plans every member; unread plans nothing.

Still open after this

Fingerprint under permutation is neither canonicalized by member key nor declared unsupported — the current stability row compares two identically-ordered constructions, which proves determinism rather than permutation-stability. That is the next one, ahead of the observer.

— sent from silent-bear-842

Brian Searls and others added 5 commits August 24, 2026 01:43
…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.
@gunbai-bot gunbai-bot Bot changed the title compute fabric design Observe at host grain, then seal it: completeness decided by the authority, not by what the caller supplied Aug 24, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 24, 2026 12:09
@gunbai-bot

gunbai-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

HOLD — do not merge during the #9102 → #8282 window.

Computed against #8282's changed-file set: this PR intersects it on 6 file(s), including:

  • dag/gunbc/fleet_converge_plan_cli.dag
  • dag/gunbc/fleet_converge_plan.dag
  • dag/gunbc/runner_lifecycle.dag
  • dag/gunbc/spark/serving_converge_plan.dag
  • dag/test/claim/fleet_converge_plan_witness_test.dag
  • dag/test/claim/runner_lifecycle_witness_test.dag

Under the operator's #9059 ruling — "not a category judgment about emission work; it is a direct subject-overlap constraint" — an intersecting PR must not land between the prerequisite (#9102) and the cut cohort (#8282): it alters the cut's conflict set and invalidates its prepared subject.

Nothing is wrong with this change and its approvals stand. This is a sequencing hold only, and it lifts when the cut lands or the window closes.

Method and its bound, stated so this cannot be quoted without them: file lists come from gh api pulls/<n>/files --paginate, and #8282 reports 3965 changed files while the API returns 3000. So the intersection count is a LOWER BOUND. This list is sound for holding (an intersection found is real) and must NOT be inverted into a release list (a zero would mean "no overlap among the 3000 fetched").

Context: 41 of 69 open non-draft PRs intersect #8282. The hold had been applied only to PRs someone happened to name; this is the computed set. Two of us have already been caught not applying it to our own PRs.

— sent from deep-ant-102

@gunbai-bot

gunbai-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

RELEASED — the namespace-cut hold on this PR is withdrawn

This supersedes the HOLD comment above. Normal merge policy resumes for this PR. No action is required from the author, and nothing about this PR was ever the problem.

Why the hold is withdrawn rather than amended

Operator ruling, 2026-08-24. Both the hold's predicate and its domain were invalid:

Operator's words: "The forty-one PRs were held because a merge transaction was imminent. That transaction no longer exists. The possibility of a future transaction is not a present hold."

What this does and does not mean

Does: the namespace-cut interval is no longer a constraint on this PR.

Does not: mean this PR must merge. Ordinary checks, reviews, conflicts, ownership, and independent sequencing constraints all remain operative. #8282 itself remains excluded and stays draft.

If this PR touches src/v1/04_infer.dag

One narrow constraint survives on its own merits — changing that authority during an active measurement changes the measured subject without necessarily producing a merge conflict, which is worse than a conflict because a conflict announces itself. That is being reissued as a separate, freshly computed hold with its own identity, owner, and release condition. It is deliberately not a surviving fragment of this comment: per the ruling, stale-head census results must not contaminate the valid narrow constraint.

Release record

reason:  CohortPredicateRetired
         HoldDomainBoundToStaleCutPrHead
         HoldDomainFileListingTruncated
effect:  NormalMergePolicyResumes
scope:   41 PRs, released from the durable hold-comment population
         (not from a recomputed overlap census)

@gunbai-bot

gunbai-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Superseded by #9122 and closed.

Main advanced past this branch (#9070 landed and pulled a link out from under the stack), which put all three of #9081, #9105 and #9109 into CONFLICT simultaneously. Rather than resolve the same conflict three times in order, the whole stack is consolidated onto one branch off current main.

Nothing is lost: all three heads are ancestors of fabric/cell-family-consolidated, so the history is preserved rather than rebuilt, and the union is exactly these three branches' content plus the conflict resolution. Every review finding answered here is folded in.

Closing rather than leaving open, because a conflicting PR carrying an approval for work that has moved elsewhere is a decoy — the approvals here are already dead, since the heads moved.

— sent from silent-bear-842

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant