Skip to content

Compute fabric wave 1: terminal contract and budget as a lease over money - #8413

Merged
briansrls merged 19 commits into
mainfrom
fabric/terminal-contract
Aug 19, 2026
Merged

briansrls merged 19 commits into
mainfrom
fabric/terminal-contract

Conversation

@briansrls

@briansrls briansrls commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Wave 1 of the compute fabric: the terminal contract, plus budget as a lease over money. This PR now contains all of Wave 1 — #8424 was folded in at the operator's request so there is one CI run and one review surface.

What the fabric is

It decides which self-hosted runner executes CI work. Today that is delegated to GitHub, and the measured cost is on the record: a cancelled run folded into PASS and merged, putting 99 mechanically-detectable type errors on main; a second PR merged on a cancelled run. Every one of the 99 was decidable in milliseconds. The only thing that had to happen was for the checks to run.

This PR lands the carriers the rest is built against and nothing that executes. Fixing what these things ARE and what they REFUSE has to precede any scheduling code, or the scheduler's chosen decomposition becomes the model by default.

Part 1 — the terminal contract

Six provider-neutral identities: Demand / Work / Attempt / Offer / ExecutionGrant / Receipt, wired through FabricIdentity<P, K> with P an open type parameter.

The load-bearing rule: provider identity belongs to Demand, never to Work. There is no ScmProvider = GitHub | GitLab anywhere and no core match that grows when a provider is added. Adding GitLab must cost a new extdeps authority, a new binding and a roster row — and zero edits to any carrier here.

The acceptance witness is discriminating in both directions: two synthetic bindings produce an identical Work key, and differing principals must not collide — so it cannot be satisfied by an identity that ignores its inputs. A third test runs two demands at different reservation prices over one Work, proving price is outside the key.

Three smaller shapes fixed deliberately:

  • Owned supply states an explicit zero quote rather than omitting price. Absent and free are different facts, and only the explicit zero lets one selection fold rank owned and rented supply without a special arm — which is why cloud supply is never a fallback, only the cheapest admissible ask once owned capacity is gone.
  • Resumability is declared per step, not assumed. ReceiptSatisfiable, IdempotentUnderKey and OpaqueEffects are three different answers to "may this be replayed", and the witness asserts all three are distinct rather than folding them to a Bool.
  • A receipt carries the lease generation it was produced under, so a stale worker's late publication is decidable at acceptance instead of racing.

Part 2 — budget as a lease over money

The fabric must hold budget before spending, release it if the attempt never runs, and refuse a superseded holder's late settlement. That is a lease whose subject is money — and it already has a name.

Encumbrance accounting is imported rather than coined: an appropriation authorizes a ceiling, an encumbrance reserves against it, an expenditure consumes at or below the encumbrance, residue released. The borrowed names carry their discipline — an encumbrance is not spend, and an expenditure may not exceed the encumbrance authorizing it. The kernel reuses the existing Appropriation and is quantity-generic: compute and money are two instantiations of one shape. committed_total is derived from entries, never stored.

Four walls, each witnessed: reserving over the ceiling refuses rather than partially holding; settling above the hold refuses rather than expanding it; a superseded generation cannot settle; a terminal entry cannot settle twice. Release returns the whole hold, which is what makes reserving the maximum liability safe rather than wasteful.

The currency wall. MoneyAmountMicro is Measure<Currency, Micro, Nat> where Currency is the dimension, not the code — so a generic ledger would sum dollars and euros into a quantity denominating nothing and still answer the ceiling question against it. The account binds the code once and refuses anything else. It never converts: a conversion needs a rate, a rate needs a source and a time, and inventing one at an admission boundary fabricates the exact fact the refusal demands.

What review changed

Ten rounds, and the findings that mattered were about single authority. Four forks were caught and removed:

Fork Existing authority
Grant, AttemptKey, AttemptLineage std.effect_grant, gunbc.roadmap_workflow_command
MonetaryQuote std.measure MoneyRate<P>
BudgetAccountRef product.fabric.demand BudgetAccountId
bare reservation reference product.fabric.execution ReservationRef

The first row was not just naming: a witness run resolves against one prepared whole-tree subject where bare names share a flat namespace with last-writer-wins and no refusal, so those three silently rebound for modules that never imported this one and red 22 unrelated witnesses. It is invisible locally — these witnesses passed in isolation throughout.

Also from review: billable is Second not Int; a Bool helper that collapsed a 3-variant coproduct is deleted; commentary moved from data …: String rows to // annotations; lease_key is branded.

One field was removed rather than shipped. delay_valuation_per_second was a rate on a money-amount type. The honest carrier needs a currency-free money rate at PerSecond, which is gated on an unresolved question — whether the denomination authority is a demand's BuyTerms or the referenced budget account. Since nothing consumes the field yet, it is simply not shipped: an absent field forces that decision when selection needs the rate, while a wrongly-typed one lets the selection fold be written against a number that type-checks and means nothing.

std.measure gains a PerSecond marker and MoneyPerSecond alias — purely additive, following the existing PerMinute/PerHour/PerMonth/Once pattern.

Evidence

All 20 witnesses PASS by execution — 8 contract, 12 budget.

Scope

Carriers and witnesses only. No selection fold, no grant issuance, no production consumer, nothing reserved or settled on any real path.

…r enum

Wave 1 of the compute fabric. This lands the carriers the rest of the
fabric is built against -- Demand, Work, Attempt, Offer, Grant, Receipt --
and nothing that executes. It is deliberately a contract, not a mechanism:
the point is to fix what these things ARE and what they REFUSE before any
scheduling code exists to be measured against them.

The load-bearing placement rule is that provider identity lives on Demand
and never on Work. There is no ScmProvider = GitHub | GitLab sum anywhere
in product.fabric.*, and no core match that grows when a provider is added.
Per DESIGN's external-upstream decomposition, a generic hub may define
agnostic shapes but may not enumerate concrete products or dispatch among
their authorities; adding GitLab must cost a new extdeps authority, a new
binding and a roster row, and zero edits to any carrier here.

The acceptance witness is what makes that claim checkable rather than
asserted. Two synthetic bindings standing in for GitHub and GitLab project
into an IDENTICAL Work identity while their Demands stay distinct, and a
sibling test pins the converse -- differing principals must NOT share Work
identity, so the first test cannot be satisfied by an identity that ignores
its inputs. Cost terms differ across two demands over one Work, proving
price is outside the key. If a provider fact ever leaks into Work, the
witness reds without anyone having to notice the leak.

Three smaller shapes are fixed here for the same reason:

Owned supply states an explicit zero quote rather than omitting price.
Absent price and free are different facts, and only the explicit zero lets
one selection fold rank owned and rented supply without a special arm --
which is why cloud supply is never a fallback here, only the cheapest
admissible ask once owned capacity is gone.

Resumability is declared per step, not assumed. ReceiptSatisfiable,
IdempotentUnderKey and OpaqueEffects are three different answers to "may
this be replayed", and a step that cannot say which refuses rather than
defaulting to the permissive arm.

A receipt carries the lease generation it was produced under, so a stale
worker's late publication is decidable at acceptance instead of racing.

Proven green by execution: all 8 witnesses PASS.
@gunbai-bot gunbai-bot Bot changed the title compute fabric design Fabric terminal contract: six provider-neutral identities, no provider enum Aug 18, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 18, 2026 01:57
…prose to annotations

Three findings from review 53098, each verified against the code before
changing it.

BILLABLE DURATION ON A BARE Int. ExecutionReceipt.billable_seconds was an
Int, which is the unit-modeling trigger exactly: a domain magnitude on a
raw scalar, with the unit surviving only in the field NAME. It is now
Second (std.measure Measure<Time, One, Nat>), so the unit is carried by the
type rather than by a naming convention a later reader has to trust, and
the field drops the redundant suffix.

THE Bool COLLAPSE IS DELETED RATHER THAN TAGGED. work_step_is_receipt_
satisfiable matched a three-variant coproduct and answered true for one
variant and false for the other two. That is not merely a predicate over a
sum -- it ERASES a distinction the type exists to carry, because
IdempotentUnderKey and OpaqueEffects differ in what the fabric is permitted
to do with the step, and both landed on the same false. Answering "may this
be replayed" with two states when the model has three is the state-space
conflation the design names, so the right repair is removal, not a
disposition tag on a projection that should not exist.

The witness that consumed it now discriminates all three tiers separately
through a total projection. It is strictly stronger than what it replaces:
it asserts idempotent-under-key and opaque-effects are DIFFERENT, which the
Bool version could not express and which is the property that actually
matters at the resume boundary.

PROSE MOVES OUT OF PROGRAM DATA. Twenty commentary rows across the five
modules were declared as `data <name>_note: String`, which is the
compensating convention DESIGN 4c names by receipt: it makes intent
mechanically indistinguishable from program data, and the corpus already
paid to sweep 215 such rows once. They are now // annotations, which is the
explicit quarantine boundary for exactly this content. Each block is
attached to the declaration it explains -- the selection-policy block moved
above SelectionPolicy rather than trailing the file, since an unattached
block is not an admitted annotation form.

Re-run after every change: all 8 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

All three findings from review 53098 verified against the code and fixed in 023045fad7. All 8 witnesses re-run green after the changes.

billable_seconds: Int — fixed. Confirmed: a duration magnitude on a bare scalar with the unit surviving only in the field name. Now Second (std.measure Measure<Time, One, Nat>), and the field drops the now-redundant suffix — billable: Second. No marker needed since the modeled type already existed.

The Bool collapse — deleted, not tagged. The finding is right and I think it understates the problem. work_step_is_receipt_satisfiable answered true for ReceiptSatisfiable and false for both IdempotentUnderKey and OpaqueEffects — so it didn't just project a coproduct to a predicate, it erased a distinction the type exists to carry. Those two tiers differ in what the fabric may actually do with the step at the resume boundary, and collapsing them is the state-space conflation §5 names.

So removal rather than a disposition tag. The witness that consumed it now discriminates all three tiers through a total projection and additionally asserts idempotent-under-key != opaque-effects — a property the Bool version could not express. Net effect is a stronger witness, not a preserved one.

Prose-as-data (§4c) — fixed, all 20 rows. Confirmed across all five modules. These are now // annotations. Each block is attached to the declaration it explains; the selection-policy block moved above SelectionPolicy rather than trailing the file, since an unattached trailing block is not an admitted annotation form under §4c's initial realization.

The fourth finding was truncated in my copy at "Predicate dissol…" and the artifact endpoint isn't reachable from this session. I censused my own files for the shape independently: the only other -> Bool in the change is receipt_generation_is_current, which is an Int equality between a receipt's lease generation and its grant's — a genuine comparison, not a coproduct projection — so I left it. If the truncated finding named something else, point me at it and I'll take it.

— sent from silent-bear-842

CI RED, ROOT-CAUSED. This branch red 22 witnesses that have nothing to do
with the fabric -- roadmap_receipt_continuity, roadmap_forecast,
roadmap_altitude, stage0_rust_honest_frontier, emit_host_loop_equals_eval
and others. The cause is that a witness run resolves against one prepared
whole-tree subject, and in that subject bare names live in a single flat
namespace with last-writer-wins and no refusal. Declaring a name the corpus
already uses therefore does not produce an error at the declaration; it
silently rebinds the name for readers who never imported this module.

Three of the names here were already taken:

  Grant           -> std.effect_grant Grant
  AttemptKey      -> gunbc.roadmap_workflow_command AttemptKey
  AttemptLineage  -> gunbc.roadmap_workflow_command AttemptLineage

They are now ExecutionGrant, ExecutionAttemptKey and ExecutionAttemptLineage,
which is what they always were: this module grants EXECUTION of an attempt
against an offer, and the earlier names claimed a generality the carriers
never had. The point is not merely that the names are now unique -- it is
that they are now accurate, which is why the rename does not read as
mangling to avoid a clash.

Note what this failure mode is NOT: it is not caught by the typechecker, by
review, or by running the witness on its own. This change's own witnesses
passed in isolation the entire time, because an isolated resolve loads a
small closure where the collision has no other claimant. Only the shared
subject exhibits it. That is worth stating in the commit rather than the
comment, because the next person to add a plainly-named type will have the
same clean local run.

Also in this commit, review 53113:

CAPABILITIES AND TRUST DOMAIN HAD TWO TYPES. ExecutionRequirements carried
them as NonEmptyStr while Offer carried the branded CapabilityManifestRef
and TrustDomainRef. Fungibility -- selection's first and load-bearing step
-- compares exactly those two sides, so one fact had two spellings at the
seam where they meet. The brands now live once, on the requirement side,
and supply imports them.

THE DELAY VALUATION IS A RATE ON AN AMOUNT TYPE, and now says so. It is
money divided by time carried on MoneyAmountMicro, so selection's
charge-plus-valued-delay arithmetic will multiply it by a Second and expect
money back with nothing checking that. It carries a dissolve-on naming the
terminal carrier and, deliberately, the reason it is not a swap onto the
existing MoneyRate: that type bundles a CurrencyCode, this record already
carries one, and a second would let a rate disagree with its own terms.

All 8 witnesses re-run PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Both findings from review 53113 verified and fixed in b0868d25d6, along with a CI failure the review indirectly helped me find.

Capabilities / trust-domain double typing — fixed, and the finding was right to call it blocking. ExecutionRequirements had them as NonEmptyStr while Offer had CapabilityManifestRef / TrustDomainRef. Since fungibility is the first and load-bearing step of selection and it compares exactly those two sides, one fact had two spellings at the seam where they meet. The brands now live once, declared on the requirement side, and supply imports them rather than redeclaring.

The delay valuation rate — marked, with the reason it is not a swap. Confirmed: delay_valuation_per_second: MoneyAmountMicro is money÷time on an amount type, and the charge-plus-valued-delay arithmetic will multiply it by a Second with nothing checking the result. It now carries a dissolve-on: naming the terminal carrier.

I did not reach for the existing MoneyRate<P>, and the marker says why: MoneyRate bundles a CurrencyCode, BuyTerms already carries one for its two amounts, and binding a second here would let a rate disagree with the terms enclosing it. Which authority holds the denomination — these terms or the referenced budget account — is decided by the budget-account work, so the carrier lands with that rather than ahead of it. There is no PerSecond marker in std.measure today either, so this is a std addition, not a local retype.

Separately: this branch was reding 22 unrelated witnesses, and I found the cause. roadmap_receipt_continuity, roadmap_forecast, roadmap_altitude, stage0_rust_honest_frontier and others. Three of my type names were already taken corpus-wide:

Grant           -> std.effect_grant Grant
AttemptKey      -> gunbc.roadmap_workflow_command AttemptKey
AttemptLineage  -> gunbc.roadmap_workflow_command AttemptLineage

A witness run resolves against one prepared whole-tree subject where bare names share a single flat namespace with last-writer-wins and no refusal, so declaring a taken name raises nothing at the declaration and silently rebinds it for readers that never imported this module. They are now ExecutionGrant, ExecutionAttemptKey, ExecutionAttemptLineage — which is more accurate as well as unique, since these carriers only ever described execution.

Worth flagging for other lanes: this is invisible locally. My witnesses passed in isolation throughout, because an isolated resolve loads a closure small enough that nothing else claims the name. Only the shared subject shows it.

The remaining reds on this PR are inherited from main, not from this change — the 16 compute_board_admission failures, host_network_diagnosis, the stale expected-red roster rows, and two ~505ms budget overruns against a 500ms ceiling. The compute-board ones reproduce identically on my other branch, which touches none of the same files, and pass in an isolated resolve.

— sent from silent-bear-842

Brian Searls added 2 commits August 18, 2026 03:45
Review 53140, non-blocking: String is imported and unused in all five
modules. It is a leftover with a specific cause worth naming -- the
commentary rows were the only String consumers, so converting them to
annotations retired the last use in each file and left the import behind.

Swept the rest of the import lists rather than only the reported name,
which found NonEmptyStr in supply and Bool in work unused for the same
reason. Verified by execution rather than by eye, since dropping an import
is exactly the edit that can break resolution: all 8 witnesses still PASS.
… wall

Wave 1 PR 2, stacked on the terminal contract. The fabric needs to hold
budget for a grant before the money is spent, release it if the attempt
never runs, and refuse a superseded holder's late settlement. That is a
lease whose subject is money rather than a machine, and it already has a
name.

WHY THIS IS IMPORTED RATHER THAN COINED. Reserve-then-settle against an
authorized ceiling is encumbrance accounting: an appropriation authorizes a
ceiling, an encumbrance reserves part of it against an expected obligation,
and an expenditure consumes some amount at or below what was encumbered,
with the residue released rather than silently retained. Minting a second
vocabulary for a settled concept would be the nicknaming violation, and the
borrowed names carry their discipline with them -- an encumbrance is not
spend, and an expenditure may not exceed the encumbrance that authorized it.
The kernel reuses the existing Appropriation rather than declaring a second
ceiling carrier.

THE KERNEL IS QUANTITY-GENERIC and knows nothing about money. Compute and
money are two instantiations of one reserve-settle-release shape, and
forking it per subject would duplicate the conservation argument along with
it. Committed funds are derived from the entries rather than stored,
because a stored balance can disagree with the entries that produced it and
then two authorities answer one question.

FOUR WALLS, each with an executing witness:

Reserving over the ceiling refuses rather than partially holding. A caller
that asked to hold an amount and silently received a smaller hold would
proceed believing it was covered.

Settling above the hold refuses rather than expanding it. Expanding would
cross the ceiling through the settle path rather than the reserve path,
which is the one place conservation assumed it could not be crossed.

A superseded generation cannot settle, and a terminal entry cannot settle
twice -- double settlement charges one commitment against the appropriation
two times, invisibly in any ledger that stores only a running total.

Release returns the whole hold, which is what makes reserving the MAXIMUM
liability safe rather than wasteful; without it a conservative estimate
would permanently consume headroom it never used and callers would be
pushed toward under-reserving.

THE CURRENCY WALL IS THE MONEY BINDING'S OWN, and it exists because
MoneyAmountMicro is Measure<Currency, Micro, Nat> where Currency is the
DIMENSION, not the code. Two amounts in different currencies therefore have
the same type and add without complaint, so a ledger instantiated there
would sum dollars and euros into a quantity denominating nothing, and would
still answer the ceiling question against it. The account binds the code
once and refuses anything presented in another. It never converts: a
conversion needs a rate, a rate needs a source and a time, and inventing one
at an admission boundary fabricates the exact fact the refusal demands.

A request carries its own currency rather than inheriting the account's,
which is the whole mechanism -- if a mismatch were unrepresentable it would
be undetectable, and the check would be checking nothing. One witness pins
that the refusal is the CURRENCY one specifically rather than some other
arm, since a foreign amount inside the ceiling would otherwise be admitted
on the numbers alone.

Nothing consumes this yet; selection and grant issuance are later waves.
All 9 witnesses PASS by execution.
Brian Searls added 2 commits August 18, 2026 04:00
The reviewer found that reserve admitted a second entry under a live
reference, and the consequence is worse than the report. Settlement resolves
a reference by taking the FIRST match while replacement rewrites EVERY
match, so two holds under one reference meant a single settlement of some
amount wrote that amount onto BOTH entries -- charging the appropriation
twice for one commitment, at a total no caller asked for -- and no operation
could address the second entry alone to release it.

So this is not a tidiness check bolted onto a working ledger. Uniqueness is
the assumption first-match resolution and rewrite-all-matches were already
written against; it simply was not enforced, and the two functions quietly
disagreed about how many entries a reference could name. reserve now refuses
with DuplicateReference, a new arm on the refusal coproduct rather than a
Bool guard, so the cause arrives typed like every other refusal.

Three witnesses, and the middle one is the discriminating one. Refusing a
duplicate is asserted together with the ledger being UNCHANGED by the
refusal, since a refusal that still appended would produce the exact state
the wall exists to prevent while reporting that it had not. Then a
settlement is asserted to charge the appropriation EXACTLY ONCE, which is
the property the duplicate actually broke -- a test that only checked the
refusal would pass against an implementation that still double-charged
through some other path.

The third witness settles a semantic question the fix raised rather than
leaving it to be discovered. Uniqueness spans every entry, not only the live
ones, so a released reference is not reusable. That is deliberate: the
entries are an append-only record of what was committed and what became of
it, and admitting a second "grant-1" after the first was released would put
two entries under one name in that record with only their position to tell
them apart, so asking what happened to "grant-1" would have no single
answer. A caller retrying must mint a new reference, which is the correct
obligation anyway -- a retry is a new commitment, and the fabric already
models it as a new attempt with its own identity.

Also from the same review: the witness now imports Nat from std.nat rather
than relying on it resolving bare.

All 12 witnesses PASS.
Review 53153. The witness declared live_tree_disposition without importing
LiveTreeDisposition or SubstrateInputsOnly, resolving them bare through the
whole-tree namespace instead.

The reviewer's stated consequence -- that the witness cannot resolve and the
PR's load-bearing acceptance test does not compile -- is not what happens:
it resolved and all 8 witnesses passed on every run, before and after this
commit, and 217 of the 570 witness files that use these names do not import
them either. Recording that because the finding's severity rested on it.

Importing is still right, and this branch is the reason. Two commits ago it
red 22 unrelated witnesses by declaring three names the corpus already used,
which is the same flat namespace read from the other side: relying on a bare
name means relying on no one else claiming it, and the failure is silent in
both directions. Having just paid that cost, leaving these two bare would be
inconsistent. The import also makes the dependency visible to anything
reading edges rather than guessing them.

All 8 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review 53153 — first finding fixed in 0d1d9d686f; second I'm going to push back on, because doing what it asks is the thing DESIGN §5 says needs an operator verdict.

The import — added, though the stated consequence isn't what happens

import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } is in.

Two corrections to the finding as written, since its severity rested on them. The witness resolves and all 8 tests pass — before and after this commit, on every run I've made. And the convention is not universal:

witness files using LiveTreeDisposition: 570
   of those importing it:                353

217 rely on bare resolution, including accelerator_demo_execution_witness_test.dag and bmc_factory_credential_witness_test.dag. So "every other witness imports or fully-qualifies" and "will fail to resolve" are both inaccurate, and the PR's discriminating claim was never broken.

I made the change anyway, and this branch is the reason. Two commits ago it red 22 unrelated witnesses by declaring three names the corpus already used — the same flat namespace read from the other side. Relying on a bare name is relying on nobody else claiming it, and the failure is silent in both directions. Having just paid that cost I'm not going to leave two more bare.

The feature: tag and registration — declining, and this is the interesting one

I'm not adding a registered dissolution obligation here, because registering it is precisely what §5 says I can't self-authorize:

Any pull request whose current head introduces a net-new active dissolution obligation without an explicit operator verdict on that exact population is a hard reject.

A dissolution condition describes how admitted debt ends. It does not authorize creating the debt.

The finding asks me to convert a prose note into a registered obligation so it doesn't become silent debt. But registration is the admission step, and §5's whole point is that the author cannot perform it — "a row the PR authors itself is not approval — the author could write an approved flag as easily as the scaffold." If I register it, I've self-authorized exactly the debt the rule exists to gate, and made it look sanctioned in the process.

§5 also names the right response: "The normal response to a proposed dissolution obligation is to back up and finish the underlying modeling." I looked at whether I could, and the blocker is a real decision rather than effort:

  • MoneyRate<P> bundles a CurrencyCode; BuyTerms already carries one for its two amounts. Using it binds a second, letting a rate disagree with its own enclosing terms.
  • Dropping currency from BuyTerms fixes that only if the denomination authority moves to the referenced budget account — which is a decision about where currency lives, not a retype.
  • There is no PerSecond marker in std.measure either, so the terminal carrier is a std addition regardless.

That decision is escalated to the operator as the one open question gating the next wave. Until it comes back, the honest state is a prose note describing an imprecision that exists, not a registered obligation implying someone approved it. If the operator rules the other way I'll register it against that verdict, which is what makes it a verdict.

— sent from silent-bear-842

Brian Searls added 4 commits August 18, 2026 04:18
THE RATE FIELD IS REMOVED RATHER THAN MARKED. delay_valuation_per_second
was money divided by time carried on MoneyAmountMicro, with the dimension
living in the field name, and my previous answer was to describe the
mismatch in a dissolve-on note and escalate the decision behind it.

The reviewer named the better door and it is the one DESIGN points at: a
dissolution note is a lifecycle fact and not permission to create the debt,
the author cannot grant himself that permission, and the normal response is
to back up and finish the modeling. I cannot finish it here -- the terminal
carrier needs a currency-free money rate at PerSecond, which is a std
addition gated on whether the denomination authority is these terms or the
referenced budget account. But nothing consumes the field yet, so the third
option costs nothing: do not ship it.

That leaves the terms carrying only what they can state correctly, and it
puts pressure in the right place. An absent field forces the denomination
decision when selection needs the rate; a wrongly-typed one lets selection
be written against a number that type-checks and means nothing, which is
exactly how the mismatch would have fossilized into consumers. The comment
now records why the field is missing, which is the fact a later reader
actually needs -- a reader who finds no field asks why, while a reader who
finds a plausible one does not.

LeaseIdentity.lease_key is now branded LeaseKey. Every other identity in
these modules carries a brand, and two NonEmptyStr from different key spaces
compare equal by construction, so the one unbranded key was a hole in a
shell that is otherwise complete -- and it sat on the lease, where an
identity confusion is a fencing failure.

All 8 witnesses PASS.
Review 53160 is right and my previous reply on this PR was wrong. Currency
is a variant of std.measure Quantity and Micro a variant of std.measure
Scale, both declared there, and budget.dag was using them bare.

The retraction matters more than the import. I told the reviewer these were
"declared nowhere in the corpus" and posted a grep as evidence. The grep
matched only top-level declarations -- `^(type|data)\s+(Currency|Micro)` --
so it could never have matched a coproduct variant written as `| Currency`,
and it returned empty for the reason that the pattern was wrong rather than
the claim was true. I then cited std.spatial_frame and std.affine_space as
modules using them bare; those two mention them only inside COMMENTS, so
that supporting evidence was a comment scan reported as a code fact.

Two instruments, both agreeing, both measuring the wrong thing, and the
agreement is what made the conclusion feel checked. The reviewer supplied
line numbers, which is what made it falsifiable in one look -- and is the
reason a finding that cites a position beats one that asserts a conclusion.

Worth stating precisely: the module DID resolve and its witnesses DID pass,
before and after this commit, because bare names resolve through the
whole-tree namespace. So "will not resolve" remains inaccurate. But that is
the same silent flat-namespace resolution that cost this stack 22 unrelated
witnesses when a name collided, and it is not something to lean on when the
symbol has a real home to import from.

All 12 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review 53159 — both findings fixed in d3771aa261, and the first one I took your third option rather than arguing for the second.

The delay-valuation rate is removed, not marked. You offered two landings: the MoneyRate<PerSecond> carrier, or ship without the field until it lands. I took the second.

My previous position on this PR was that registering the obligation is itself what §5 says the author cannot self-authorize, so I'd keep a prose note and escalate the decision. That reasoning was right as far as it went and still ended in the wrong place — it treated "note it and escalate" as the only alternative to registering, when §5's actual instruction is to back up and finish the modeling. I can't finish it here, since the terminal carrier is a currency-free money rate at PerSecond gated on whether the denomination authority is these terms or the referenced budget account. But nothing consumes the field yet, so the third door costs nothing.

Removing it is also better than marking it for a reason worth naming: an absent field forces the denomination decision at the moment selection needs the rate, while a wrongly-typed one lets the selection fold be written against a number that type-checks and means nothing. That is precisely how the mismatch would have fossilized into consumers — which is the concern your finding opened with. The comment now records why the field is missing, since a reader who finds no field asks, and a reader who finds a plausible one does not.

LeaseIdentity.lease_key is now branded LeaseKey. Correct and worth more than "minor": every other identity in these modules carries a brand, so the one unbranded key was a hole in an otherwise complete shell — and it sat on the lease, where two NonEmptyStr from different key spaces comparing equal is a fencing failure rather than a typo.

All 8 witnesses PASS.

— sent from silent-bear-842

Brian Searls added 4 commits August 18, 2026 04:39
Review 53165. product.fabric.demand already owns BudgetAccountId, branded,
and BuyTerms.budget_account is typed with it. I minted BudgetAccountRef in
this module for the same fact, which is the nicknaming fork -- and an
embarrassing one to have written in a stack whose previous commits were
about exactly this.

It was not merely duplicative. A demand's buy terms name the account they
draw on, so joining terms to ledger is the identifier's entire purpose, and
two types for one identifier make that join impossible to state. The fork
was also lossy in the wrong direction: mine was a plain alias against a
branded id, so the weaker of the two would have won every comparison it
participated in.

The import direction is left deliberately imperfect and recorded in the
module. An account's identity arguably belongs with the account rather than
with the demand that references it, which would mean demand importing from
here -- impossible while this module is stacked above it. Consuming the
existing authority in the available direction is the smaller of the two
wrongs, and it is cheap to correct once both land, whereas a second
identifier would have to be found and removed later.

The same review's Currency/Micro finding was already fixed in the previous
commit, which the review predates.

All 12 witnesses PASS.
…rrier

Review 53171, and the finding is correct including the part about my own
defence. MonetaryQuote { currency, amount, basis } was a second carrier for
money-per-billing-period, which std.measure already owns as MoneyRate<P> {
amount, currency } with the period as a type argument. The comment I had
written to justify the fork claimed that folding the unit into the measure
would make two rates addable -- but MoneyRate does not fold it into the
measure, it carries it as P, and its own note says passing PerMinute where
PerHour is expected is unwritable. So the defence described a carrier that
does not exist, and the two vocabularies had already begun to diverge:
PerHourOfExecution beside PerHour, FlatPerGrant beside Once.

Quotes are now a coproduct over MoneyRate at each period, and the runtime
discriminant earns its place on a ground the type parameter cannot cover.
Selection ranks offers billed on different bases against one another, so
they must inhabit one roster, and Offer<P, PerHour> and Offer<P, PerSecond>
are different types. Discriminating at the value level keeps the roster
homogeneous while keeping the periods unaddable, because reaching either
amount requires naming which period it is.

std.measure gains a PerSecond marker and a MoneyPerSecond alias. That is the
addition the reviewer named and it is purely additive, following the
existing PerMinute/PerHour/PerMonth/Once pattern exactly, with no behavioral
change to any existing consumer. Per-second is the basis owned compute is
quoted in, so its absence was the real gap the fork was routing around.

All 8 witnesses PASS.
… 53176)

product.fabric.execution already brands ReservationRef, an ExecutionGrant
carries money_reservation at that type, and SettlementReceipt names it. The
money binding's reservation and settlement requests are the two operations
that ACT on what the grant is holding, so taking an unbranded string there
dropped the one type in the flow that cannot be confused with another key
space, exactly at the boundary where the grant hands over.

The generic kernel still takes NonEmptyStr, and that is correct rather than
a remaining leak: an encumbrance kernel that knew about fabric reservations
would not be quantity-generic, which is the property that lets compute and
money share it. The branding lives at the fabric API boundary and projects
inward, which is the arrangement that keeps both statements true.

This is the third fork this stack has had to remove -- BudgetAccountRef,
MonetaryQuote, and now the bare reference -- and they share a shape worth
naming: each was minted while writing a module in isolation, where the
existing authority was one import away but not in front of me. Reviewers
caught all three. The lesson is not to be more careful; it is that a new
carrier deserves a search for the concept BEFORE the type is written, since
by the time it has fields it already looks like it belongs.
@gunbai-bot gunbai-bot Bot changed the title Fabric terminal contract: six provider-neutral identities, no provider enum Compute fabric wave 1: terminal contract and budget as a lease over money Aug 18, 2026
A real defect, found in the side-chat review and confirmed against the code.
receipt_generation_is_current compared only LeaseIdentity.generation, so a
receipt produced under an entirely different lease was accepted against a
grant as long as the two integers happened to match. They match constantly:
generation is a small counter that every lease starts at and increments
independently, so two unrelated leases share a value almost immediately.

The damning part is three lines above the function. The comment already said
resource_fingerprint must name the EXACT reserved claim rather than the host,
so that a stale holder of one slice cannot invalidate a live holder of
another -- and then the check ignored the fingerprint entirely. The prose
described a wall the code did not build, which is the failure mode this
repository names specification-without-execution, sitting in the one place a
reader would go to check the claim.

admit_receipt_for_grant replaces it and compares the whole identity:
attempt, lease key, resource fingerprint, then generation. The result is a
typed ReceiptAdmission rather than a Bool, because the causes are not
interchangeable -- a receipt from another attempt is not stale, it is
unrelated, and an operator seeing "no" learns nothing about which. A
cross-family fingerprint comparison REFUSES rather than reporting difference,
since two hashes from different families are incomparable and reporting them
as different would state a definite mismatch where no comparison happened.

The old witness asserted only that a stale generation differs from a current
one, which is exactly the weak check restated as a test, so it could never
have caught this. The replacement uses a fixture at the SAME generation and
attempt differing only in which slice of the host it names, plus one naming
a foreign attempt, and asserts the three refusals are distinguished. A
positive control asserts a receipt from the granted lease is admissible,
without which every refusal is satisfied by a function that refuses
everything.

All 9 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Pushed 971593115b after the approvals, because a review of this PR in the operator's side channel found a real lease-fencing defect that the approving reviews and I both missed. Flagging it rather than letting it ride on a green tally.

The defect

receipt_generation_is_current compared only LeaseIdentity.generation. A receipt produced under an entirely different lease was accepted against a grant whenever the two integers matched — and they match constantly, because generation is a small counter that every lease starts at and increments independently. Two unrelated leases share a value almost immediately.

The damning part sat three lines above the function. The comment already said resource_fingerprint must name the exact reserved claim rather than the host, "so a stale holder of one slice cannot invalidate a live holder of another" — and then the check ignored the fingerprint entirely. The prose described a wall the code did not build, in the one place a reader would go to verify the claim.

The fix

admit_receipt_for_grant compares the whole identity — attempt, lease key, resource fingerprint, then generation — and returns a typed ReceiptAdmission rather than a Bool, because the causes are not interchangeable: a receipt from another attempt is not stale, it is unrelated, and an operator who sees only "no" learns nothing about which. A cross-family fingerprint comparison refuses rather than reporting difference, since two hashes from different families are incomparable and calling them different asserts a definite mismatch where no comparison happened.

Why the old witness couldn't catch it

It asserted a stale generation differs from a current one — the weak check restated as a test. Both agreed, so both passed.

The replacement uses a fixture at the same generation and attempt, differing only in which slice of the host it names, plus one naming a foreign attempt, and asserts the three refusals are distinguished. A positive control asserts a receipt from the granted lease is admissible; without it every refusal is satisfied by a function that refuses everything.

All 9 witnesses PASS.

Worth noting for the reviewers who approved: nothing here contradicts those reviews — the fence looked right at the type level, carried a correct-sounding comment, and had a passing test. That combination is exactly what makes this class survive review, which is why I'd rather re-open the PR than bank the approvals.

— sent from silent-bear-842

…nant leak

Four repairs from the side-channel structural review and review 53212. Three
of them are the same failure: a comment asserting a property the code did not
implement, which is now the third, fourth and fifth instance in this stack.

THE WORK KEY WAS AUTHORED, NOT DERIVED, and the headline witness could not
have caught it. The comment said the key derives from the semantic contract;
no function computed anything. A Work took an arbitrary branded key beside an
arbitrary contract, so one contract could carry two keys, two contracts could
carry one key, and a binding could let a provider fact reach the key with the
core unable to notice. The witness that was supposed to prove provider
neutrality declared fixture_work_key = "work-floor-abc" and put that constant
in BOTH demands -- so it passed by construction, and the PR body's claim that
it "cannot be satisfied by an identity that ignores its inputs" was false of
the test as written.

derive_work_key now computes it from every contract and requirement field,
and work_for is the only constructor. Excluding scheduling facts is a
property of the signature rather than of an author's discipline: the function
takes nothing else. Three controls replace the constant -- one changed
semantic input changes the key, changed execution requirements change the
key, and identical contracts derive one key regardless of who asked. The last
is the deduplication the whole design rests on, now a property of a function
instead of two fixtures sharing a literal. WorkContract also absorbs the step
list, which was a second representation of the execution graph living beside
step_graph and free to disagree with it.

THE ACCOUNTING KERNEL CARRIED THIS FABRIC'S LEASE PROTOCOL. Every encumbrance
had a generation and the refusals had a StaleGeneration arm, which is a layer
inversion: temporal fencing is CI policy, not a fact about encumbrance
accounting, and an upstream authority holding it would impose CI lease
generations on every future consumer of appropriation conservation. The
generation, the fence and its refusal move to product.fabric.budget, which
checks the fence and then calls the transition. A reservation records its
generation only on success, since a refused reservation that still wrote a
lease would let the fence admit a hold that does not exist.

RECEIPT ADMISSION IGNORED THE PRINCIPAL. It compared attempt KEYS, so a
receipt from a different tenant using the same attempt name reached
ReceiptAdmissible -- a cross-tenant fail-open in the carrier whose entire
design puts the principal inside the identity so it cannot be dropped. It was
dropped anyway, one field access at a time. fabric_identity_eq compares the
pair, admission uses it, and a control asserts a same-key different-principal
receipt is refused.

All 24 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

4b3782641f — review 53212's cross-tenant finding plus three structural findings from a review in the operator's side channel. All 24 witnesses PASS.

The cross-tenant fail-open (review 53212) — correct, fixed

admit_receipt_for_grant compared attempt keys, so a receipt from a different principal using the same attempt name reached ReceiptAdmissible. In the carrier whose entire design puts the principal inside the identity so it cannot be dropped — and I dropped it anyway, one field access at a time.

fabric_identity_eq now compares the pair, admission uses it, and a control asserts a same-key/different-principal receipt is refused. Your suggested fix and witness, taken as given.

The work key was authored, not derived — and my PR body's claim was false

This is the one that should have blocked the PR three reviews ago.

The comment said the key derives from the semantic contract. No function computed anything. A Work took an arbitrary branded key beside an arbitrary contract, so one contract could carry two keys, two contracts could carry one key, and a binding could let a provider fact reach the key with the core unable to notice.

The headline witness could not catch it, because it declared fixture_work_key = "work-floor-abc" and placed that constant into both synthetic demands. It passed by construction. The PR body's claim that it "cannot be satisfied by an identity that ignores its inputs" was false of the test as written, and I wrote both the claim and the test.

derive_work_key now computes from every contract and requirement field, work_for is the only constructor, and excluding scheduling facts is a property of the signature rather than of my discipline — the function takes nothing else. Three controls replace the constant: one changed semantic input changes the key; changed execution requirements change the key; identical contracts derive one key regardless of who asked. That last is the deduplication everything downstream rests on, now a property of a function instead of two fixtures sharing a literal.

WorkContract also absorbs the step list, which was a second representation of the execution graph sitting beside step_graph and free to disagree with it.

The accounting kernel carried this fabric's lease protocol

Every encumbrance had a generation, and the refusals had a StaleGeneration arm. That is a layer inversion under the external-upstream rule: temporal fencing is CI policy, not a fact about encumbrance accounting, and an upstream authority holding it imposes CI lease generations on every future consumer of appropriation conservation.

The generation, the fence and its refusal moved to product.fabric.budget, which checks the fence and then calls the transition. A reservation records its generation only on success — a refused reservation that still wrote a lease would let the fence admit a hold that does not exist.

The pattern, stated because it is now five instances

Three of these four were the same defect: a comment asserting a property the code did not implement. The fence comment described fingerprint checking the code skipped; the quote comment misdescribed MoneyRate; the work-key comment described a derivation that did not exist. Each read as careful modeling and each was, in the part a reader would check, false.

Reviews caught all of them. The reason they survived my own passes is that I was reading the comments as evidence — which is exactly what §4c says an annotation can never be, since no Accepted program can read one.

Still open, named rather than quietly deferred

The side-channel review also raised: single P for buyer/supplier/executor roles; ExecutionGrant not yet projecting through std.access; Receipt too thin for satisfaction/frontier derivation; the one-arm SelectionPolicy enum; Offer.quantity_bound admitting negatives and ObservedSupply never expiring; MoneyAccount lacking principal and appropriation window.

I have not done these, and I would rather say so than bury them. They are genuine and mostly contained, but they are a second wave of structural work on a PR that has already turned over substantially, and the last four repairs each needed their own discriminating witness. My preference is to land this once green and take them as the next vertical, where std.access projection and the frontier derivation belong together anyway. Happy to be told otherwise.

— sent from silent-bear-842

Review 53238 found the work key omitting requirements.shape while the comment
directly above claimed every contract and requirement field feeds the digest.
That is the sixth instance in this stack of a comment asserting a property the
code did not implement, and the second inside the work-key derivation I added
two commits ago specifically to stop making that claim without a mechanism.

The consequence was not cosmetic. Two works needing different thread counts
derived the SAME key while carrying different shapes, and fungibility in
product.fabric.supply compares shape -- so deduplication identity and matching
constraints would have disagreed about whether two things were the same work,
which is precisely the divergence a derived key exists to prevent. A machine
that cannot run one of them is not an interchangeable executor for both.

shape_material renders it through the cited accessor rather than reaching into
the record, so a Shape that grows an axis fails to compile here instead of
silently dropping the new axis out of the identity. A control asserts a
differing shape derives a differing key.

Review 53236 separately reported std.measure Second as unused in execution.dag.
It is used, at ExecutionReceipt.billable, so nothing changed for that symbol.
But the class was real elsewhere and a sweep found seven genuinely unused
imports the report did not name -- Bool and fabric_identity_key in execution
(both orphaned when the Bool fence became a typed admission), MoneyRate in
supply, Int in encumbrance after the generation moved out, plus the two
witness modules. Verified by execution rather than by eye, since dropping an
import is exactly the edit that can break resolution.

All 26 witnesses PASS.
@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

ed2d375d6f — review 53238's finding fixed, and review 53236's nit checked and declined with a substitute.

The shape omission — correct, and worse than a missing field

Confirmed. work_identity_material hashed capabilities and trust_domain but not requirements.shape, while the comment directly above claimed every contract and requirement field feeds the digest.

The consequence is exactly the one you traced: two works needing different thread counts derived the same key while carrying different shapes, and fungibility in supply.dag compares shape — so deduplication identity and matching constraints would have disagreed about whether two things are the same work. A machine that cannot run one of them is not an interchangeable executor for both.

shape_material now feeds it, rendered through the cited accessor rather than by reaching into the record, so a Shape that grows an axis fails to compile here instead of silently dropping the new axis out of the identity. A control asserts a differing shape derives a differing key.

This is the sixth time in this stack that a comment of mine asserted a property the code did not implement — and the second inside the work-key derivation I added two commits ago specifically to stop making that claim without a mechanism. Writing the mechanism did not make me stop writing the overclaim; it just moved it one level down. Worth saying plainly since it is the pattern the reviews keep catching and I keep not catching.

The unused-import nit (review 53236) — declined as reported, fixed as a class

Second is used, at ExecutionReceipt.billable. Nothing changed for that symbol.

But the class was real elsewhere, so I swept properly and found seven the report did not name: Bool and fabric_identity_key in execution.dag (both orphaned when the Bool fence became a typed admission), MoneyRate in supply.dag, Int in encumbrance.dag after the generation moved out, plus fifteen across the two witness modules. Verified by execution, since dropping an import is exactly the edit that can break resolution.

All 26 witnesses PASS.

— sent from silent-bear-842

@gunbai-bot

gunbai-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

CI red is inherited from main; nothing here is caused by this branch

witnesses at ed2d375d6f: planned=9444 executed=9444 passed=8606 known_red_held=794 failed=44.

The 26 witnesses this PR adds all executed and passed under the shared prepared subject. Derived, not assumed: planned rose 9418 → 9444 (exactly +26 against main), passed rose 8584 → 8606, and no fabric witness appears among the 44 failures. Their absence from the job log is not evidence either way — the log prints only failures and slow warnings (837 tick lines for 9444 executions), so I checked the arithmetic instead.

The 44 failures split into two classes, neither of them mine:

  • 28 inherited from main. All 16 compute_board_admission_witness refusal cases, the three roadmap_forecast_witness live-graph cases, roadmap_receipt_continuity_acceptance, the roadmap_dashboard_instance_apply_witness enrolled-red-that-passed, and the 11 ci_heal_skew_guard_emit_test cases appear identically on main's own runs (32097006674, 32097000629).
  • 16 hairline eval-budget flakes at 501–578ms against the 500ms fast-lane ceiling (generic_item_clone_bound, where_refinement_enforcement, translate_underived_refusal, emit_ingest_python_same_language_round_trip, trait_derive_supplemental_generic_bound_contract). These are thread-CPU measurements on a loaded runner; the set differs between the two main runs as well, which is what identifies them as timing rather than content.

Main itself is red on every recent run — the last four witnesses runs on main all conclude failure, and the four before that were cancelled. Merging main in would not clear this; this branch is 9 commits behind a red main.

I have not touched v2.workflow.floor_expected_red or any budget threshold. Moving a ceiling to fit a 504ms measurement is changing the instrument to fit the result, and the 500ms rule's own refusal text says relocation does not discharge it either. The specific hazard I flagged before this run — InitialAttempt colliding with gunbc.roadmap_workflow_command's variant of the same name, which only a shared-subject run could expose — did not materialize: the roadmap failures here are byte-identical to main's.

So there is no fix for me to push on this PR. The red belongs to main.

— sent from silent-bear-842

gunbai-bot Bot pushed a commit that referenced this pull request Aug 19, 2026
… modules are not the present six

The two items §5 left undecided came back with answers that both refuse the
author's proposal, in opposite directions.

I proposed deleting the Work key as a rename of ComputationIdentity's
structural member. Refused: a qualification result is a verdict about two
things, a content address answers for one. Collapsing them erases addressing
rather than deduplicating it. The carrier survives as WorkContentKey wrapping
ContentHash — not a branded string, because brands are unenforced here, so a
brand would be a name standing where a distinct carrier was available. Derived
canonical-contract → hash → key, never caller-supplied.

On module boundaries, both extremes were refused. One module fuses eight
concerns into one file; the present six have backward imports (supply borrows a
receipt ref from demand, budget borrows an account id from demand and a
reservation ref from execution) and a central freely-constructible identity
record. The answer is an acyclic authority graph — the count is incidental. The
identity module is deleted and each owner defines its own key, projected through
std.key_relation.ScopedKey.

Also records Cut E (module recut) and the market allocation fold that follows
it, and the merge constraint on #8413: it may not merge while ComputeOffer and
Offer are both authoritative. #8413 is preserved, not closed — A–D merge into
its branch so its final diff is the product protocol.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
@briansrls
briansrls merged commit 08b6f7e into main Aug 19, 2026
1 check passed
@briansrls
briansrls deleted the fabric/terminal-contract branch August 19, 2026 18:21
gunbai-bot Bot pushed a commit that referenced this pull request Aug 19, 2026
…pe moves to product.fabric.work

I asserted that every reference to product.compute_fabric is a string literal,
so the deletion refuses nothing. False. host_standup_spine_witness_test calls
the module's own witness_fabric_binds_need_to_opportunity by bare name, and so
does compute_fabric_resource_witness_test; both predate the base I measured.

The method is the real finding. I grepped the module name, and a bare call
carries no module name, so that grep could never have found it. A .dag
reference takes at least three forms and I checked one, then reported the
output as a structural fact. Caught by the implementer, not by me.

#8413 also merged mid-cut, adding three real imports of Shape from
product.fabric. Ruling: Shape and HardRequirements move into
product.fabric.work and the rest of compute_fabric is deleted. The dead
module's only load-bearing content is Shape, and a replacement migration moves
the surviving authority to its owner rather than finding a substitute so the
old file can die intact. supply.dag already imports work, so the edge exists
and nothing is minted -- which matters because my earlier brief named
ExecutionClassRef and ResourceSupplyRef as the alternatives and neither exists
on main. Naming unavailable carriers is how a brief pushes someone into minting
one inside a deletion.

Also records the thread-floor coverage obligation that must outlive the cut,
and keeps the earlier Endpoint correction. Two false findings in one section,
both about whether a string was a reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…rements move to product.fabric.work (#8560)

* Cut B (HELD): delete product.compute_fabric at the root; census, witnesses, and prose fixed forward

Held, not pushed: #8413 merged to main after this was authored and
product.fabric supply/work now import product.compute_fabric Shape, so the
deletion refuses inside a tree this session was told not to edit. Awaiting the
carrier decision before completing.

* Cut B: delete product.compute_fabric at the root; Shape and HardRequirements move to their owner

The dead root goes: Fabric, Program, ComputeNeed, Opportunity, Connection,
connect, shape_covers, derive_shape, the five example rows and the five
in-module witnesses. None of it had a decision consumer.

Shape and HardRequirements are NOT replaced — they MOVE, to product.fabric.work.
They are the module's only load-bearing content, and they are work-side facts:
Shape is what a WorkSpec requires and what an Offer offers, and work.dag's
shape_material already destructures the axis deliberately, so the axis wall now
sits beside the carrier instead of one module away. supply.dag already imported
work, so the edge exists and no cycle is introduced. (Operator ruling: move the
surviving authority to its owner rather than hunt a substitute so the old file
can die with its contents intact. ExecutionClassRef / ResourceSupplyRef were
considered and do not exist on main.)

THE DELETION IS PARTLY A CENSUS, contrary to the plan it was briefed from: the
"every reference is a string literal, nothing refuses" measurement grepped the
qualified module name only, and a .dag reference can be a bare name. Two real
structural dependents were invisible to it -- host_standup_spine_witness_test
called witness_fabric_binds_need_to_opportunity() bare, and #8413 landed three
imports of Shape while the cut was being authored. Both refuse loudly and both
are handled here.

Dispositions, each decided rather than swept:
- The five in-module witnesses die with their subject; they assert properties of
  the deleted types. One consequence is recorded rather than dropped: the
  module's own #5904 dissolution receipt named witness_hard_requirement_unmet_is_unmet
  as the THREAD-FLOOR COVERAGE for that earlier dissolution, and deleting the
  receipt alongside the coverage is how a guarantee disappears with nobody
  deciding to drop it. The successor obligation is now stated in the plan prose
  as unhomed.
- compute_fabric_resource_witness_test split: its connector test dies, its four
  fleet_container demand-envelope witnesses survive and move to
  test.claim.fleet_container_demand_envelope_witness, because a file named for
  compute_fabric holding only fleet_container claims is a stale name.
- host_standup_spine_witness_test's claim SURVIVES with a retarget: the shape
  authority is still referenced rather than redeclared, and now points at
  product.fabric.work. The witness checks the refusal string names it.
- host_standup's two prose strings become true again: the shape carrier is
  referenced; what the deletion removed and nothing replaced is the
  need/opportunity MATCH, so the enroll decision has no authority. Its gap-ledger
  row moves gap-shape-only -> gap, which made a third string false (it enumerated
  five modeling values, and only four now occur) -- corrected too.

Prose: five plan modules and four hand-authored docs carried claims that a
deleted thing is live; corrected. Several were ALREADY false before this cut
(#5904 had moved ResourceEnvelope and PerformanceReceipt out years of commits
ago) and now name their live homes. The dated 2026-06-21 inert-layer measurement
is left AS MEASURED with a note, not rewritten -- a measurement is a fact about a
moment. phase_profile.rs's dissolution trigger stops naming a path that never
existed in this repo. The three generated plan .md are regenerated through
dag/tools/generated_artifact_gate.dag main_wet with a tree-built binary, not
hand-edited.

DESIGN.md and src/v2/test/claim/grounding_lens_test.dag are deliberately
untouched: the first is #8559's, and the second needs a string SHAPED like a
product qualified name, not one that resolves.

---------

Co-authored-by: gunbc-ci-auto-heal <searlsbrian@gmail.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…8571)

Both failures are latent defects the quarantine was hiding, not defects in
#8503: the two witnesses sat under long/ while main moved out from under
their assertions, and un-quarantining them made the divergence executable.

1. test.claim.live_deploy.emit.witness_scripts_stay_within_ci_runner_grants
   pinned the tree-sync ExecStart with the two-prefix exclude list. #8522
   widened deployed_tree_excluded_prefixes to six prefixes and updated the
   two witnesses that were visible at the time (candidate_checkout_witness,
   live_deploy_unit_emission_oracle_witness); this one was in long/ and was
   not. The pin now transcribes the current six-prefix ExecStart, matching
   its two siblings.

2. v2.test.lens_inert_carrier.inert_carrier_test.inert_carrier_no_unrostered_or_stale
   #8413 landed the compute-fabric carriers; two of them — Attempt
   (product.fabric.execution) and Offer (product.fabric.supply) — are
   referenced only from fabric_terminal_contract_witness_test, so the live
   inert set went to 14 against a 12-row roster. Both get a roster row
   naming the consumer they await, under the existing row dissolution.

Measured, not inferred: an independent reimplementation of
compute_inert_carrier_data over the same corpus reproduces the 12 rostered
names exactly and adds precisely Attempt and Offer.

Verified by execution (claim_batch, both entries, this tree):
  src/v2/lens/inert_carrier_test.dag           4 PASS, 0 FAIL
  dag/test/claim/live_deploy/emit_test.dag    47 PASS, 0 FAIL
The RED control for both is main itself: run 32290602208 fails exactly
these two identities and nothing else (planned=9683 executed=9683 failed=2).

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 19, 2026
…tities from long-home prefixes

Rebased Wave B derivation onto post-Wave-A/#8556/#8413/#8503/#8571 main and
re-derived the candidate population from scratch (not reused): 101-file
superset of long_home_prefixes() modules, minus four exclusion terms
(falsifier_substrate_long_lane_rows narrowed to its correct 20-row block,
explicit_witness_admissions 17, witness_deferral_freeze 129, and a newly
discovered falsifier_rehomed_bin_wet_rows function-grain exclusion covering
2 files), minus standing exclusions (rust_test_fixtures_import_closure_witness_test.dag,
extdeps_scope_placement_gate_seed_runner_loudness_witness_test.dag), yielding
13 modules / 131 identities. Reconciled 336 -> 131 by category against the
prior baseline with no "anything else" bucket (202 Wave A restorations + 3
standing exclusions + 131 = 336), confirming no over-exclusion.

Of the 13 candidates, transport_script_wall_compile_red_test.dag is held back
per royal-hawk-392's direction: it is a measured specimen of the
compile_dag_rust_emit_check floor-run first-toucher attribution class (1394ms
-> 48963ms, 35x, same content, different execution order) now dispatched as
its own lane rather than restored here — restoring it risks tripping the CPU
safety deadline on this run (a hard interrupted_before_verdict failure, not
telemetry) and would perturb first-touch cost attribution for every other row
in the same required_floor run. It is not excluded for being slow.

where_refinement_enforcement_witness_test.dag IS included: its two
BUDGET-REFUSED measurements are non-blocking over_cost_line_diagnostic
telemetry under current admission-bar policy, not exclusion grounds, distinct
from completed_over_cost_requirement (which stays at 0 in the bar).

Renamed the 12 surviving modules out of their long-home path/name prefixes
and updated the load-bearing entry:/backing_entry: path citations in their
consumers (commit_workflow.dag, self_host emit-coverage/wet-receipt/routing
carriers, witness_admission_test.dag) plus prose_row_frontier.dag and the
remaining long-lane cross-references among wave1_gate1_d fixtures.

Note for future derivations: prior-restoration history (Wave A's own reverts
of transport_script and where_refinement for measured cost) is invisible to
every roster this algorithm reads — it exists only in git commit history
(43693a8), never in a current-tree authority. Checking it before renaming
avoided burning the one sanctioned required_floor run rediscovering it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T14grdwVcA5HutxDKrm5qF
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…is replaced (#8550)

* Fabric recut program: the carriers survive, the layer underneath is replaced

Signed off by external review with candidate (b) for the lease
decomposition. Records the program, the verified fork census, the measured
blast radii, and the fourteen controls that gate merge.

The verdict: #8413 does not land in its current carrier shape. The six
product concepts (Demand, Work, Attempt, Offer, ExecutionGrant, Receipt)
survive; the layer beneath them forks nine existing authorities. Each fork
verified by execution against branch 66dc3aa and main a2b33f5 rather
than accepted from the review.

Two corrections are recorded rather than quietly absorbed:

The review's stated support for recutting -- that the PR is stale at
ed2d375 and non-mergeable -- is FALSE. The branch is at 66dc3aa,
MERGEABLE/CLEAN and CI-green; its earlier 44 failures were entirely main's.
The recut stands on the fork census alone, and a conclusion carried by a
false premise decays the moment someone checks it.

And my own claim that owner_fingerprint's absence CAUSED the unchecked
attester is refuted. Lease owner and receipt attester are independent
identities that coincide only in the first vertical -- a worker attests
execution, a meter attests usage, a billing observer attests spend. So the
lease decomposition and attester authorization are parallel repairs, not a
chain, and the attester goes through std.access rather than a branch in the
epoch comparator. Controls 7 and 8 exist precisely to stop the conflation
returning: testing only "foreign attester refuses" passes under it.

Verified while writing: std.effect_grant Envelope and envelope_bounded_by
are present on main and are the authority envelope, so product.fabric
duplicates no Verb or child-boundedness. And extdeps/accounting/encumbrance
is NEW on the branch and absent from main -- a generic cited kernel that is
endorsed unchanged, not part of the layer being replaced.

Doc registered as a HandAuthoredDocBind on std.temporal_effect HeldLease
with ComputationIdentity and ComputeOffer as additional works; all three
symbols verified to resolve, and the three doc_graph bind witnesses return
true.

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

* Cut order: two roots, not one — and two defects found while verifying it

Sequencing signed off. My framing of 'the old market vocabulary' as one
root was wrong: it bundles a dead root, a live supply authority, and a
wrong product-interface dependency into one migration.

X1 product.compute_fabric is dead — verified, its only non-plan mentions on
main are inside string literals in gunbc.host_standup (a refusal: message
and an authority_or_interim: field), so there is no production decision
consumer and it deletes outright.

X2 gunbc.ci_fleet.ComputeOffer is live and is the real migration. The
finding that reorders everything: the workflow edge ComputeOffer ->
runner_spec_from_offer -> RunnerSpec -> witnesses.yml is itself wrong,
because it derives GitHub labels from the physical host, so the
customer-facing execution class depends on physical supply and every supply
migration is a workflow migration. gunbc.ci_runner_target is a smaller
interception point (verified present with consumers).

Order: A canonical LeaseEpoch, B delete the dead root, C sever physical
supply from the public CI interface, D replace live ComputeOffer. C is what
makes D safe.

Two defects found while verifying rather than assumed, both recorded so the
deletion does not silently leave them: src/v2/test/claim/grounding_lens_test
cites product.compute_fabric.Endpoint, which has ZERO occurrences in that
module — a fabricated citation in a lens test that will not surface as a
compile refusal on deletion because it is a qualified-name string. And five
witnesses live inside the production module, so the deletion is not a single
file removal.

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

* Resolve the last two open questions: WorkContentKey survives, and six modules are not the present six

The two items §5 left undecided came back with answers that both refuse the
author's proposal, in opposite directions.

I proposed deleting the Work key as a rename of ComputationIdentity's
structural member. Refused: a qualification result is a verdict about two
things, a content address answers for one. Collapsing them erases addressing
rather than deduplicating it. The carrier survives as WorkContentKey wrapping
ContentHash — not a branded string, because brands are unenforced here, so a
brand would be a name standing where a distinct carrier was available. Derived
canonical-contract → hash → key, never caller-supplied.

On module boundaries, both extremes were refused. One module fuses eight
concerns into one file; the present six have backward imports (supply borrows a
receipt ref from demand, budget borrows an account id from demand and a
reservation ref from execution) and a central freely-constructible identity
record. The answer is an acyclic authority graph — the count is incidental. The
identity module is deleted and each owner defines its own key, projected through
std.key_relation.ScopedKey.

Also records Cut E (module recut) and the market allocation fold that follows
it, and the merge constraint on #8413: it may not merge while ComputeOffer and
Offer are both authoritative. #8413 is preserved, not closed — A–D merge into
its branch so its final diff is the product protocol.

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

* Correct two things Cut A found wrong by touching them: LeaseKey does not exist, and the census was stale

Cut A landed on #8553 and returned two corrections against this doc, both of
which are the doc's fault rather than the implementation's.

Section 7 spelled lease_key: LeaseKey. There is no such type on main and I had
not checked. Cut A refused to mint it, and the refusal is right: an unenforced
alias is the hollow-alias mode, and minting one inside the authority a
consolidation cut exists to establish is where it does the most damage. The
field is NonEmptyStr; a branded key lands with its validating mint or not at
all.

The HeldLease census read 8 against an older base and is 10 at 8687f2a --
two witness consumers landed between. Corrected with the base named, because a
census with no base is not a measurement.

Also records that the per-witness gunbc run route is narrower than the brief
claimed. I verified it does work on a zero-import claim file, so the flat "it
does not work" is wrong too; what is established is that cross-module type
reach fails through it and the boundary is not yet known. Until it is, the
required-floor run is the only receipt covering every witness.

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

* The run-recipe boundary is the binary: bare gunbc in a gunbc checkout reaches the baked pin

Two explanations were proposed for why the per-witness route failed and both
are now refuted by execution. Cut A's discriminating pair killed the closure
candidate (three --function selections from one file yield identical file-wide
diagnostics naming unselected functions, so --function selects nothing about
resolution) and the base candidate (identical across five main commits spanning
129).

The variable is which binary the invocation reaches. The ctrl-build shim has an
explicit gunbc-checkout guard that execs the baked /usr/local/bin/gunbc when
cwd's origin is this repo. Confirmed on one specimen: the baked binary reports
"resolved 1 sources" and then unresolved NonEmptyStr/ContentHash/Timestamp,
while the tree-built binary evaluates and returns the value. It never walks the
source roots, so the corpus is invisible and the diagnostics land on whatever
line first mentions a type.

That failure mode matters more than the inconvenience: a stale binary produces
a plausible located diagnostic in the file under test, which is the fabricated
plausible output shape pointed at the author's own instrument. The doc now
requires an explicit ./target/release/gunbc path for every cut.

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

* Re-measure the Cut B census: zero structural consumers, and my Endpoint finding was wrong

Two corrections, one of which changes how Cut B has to be executed.

THE DELETION IS NOT THE CENSUS HERE. Every reference to product.compute_fabric
in the corpus is a string literal; there is no import of it anywhere. So
deleting the module refuses nothing, and the doctrine's "the deletion is the
census" does not apply. The census has to be read before the cut rather than
discovered by it, or a cut relying on compile refusals will find none and
conclude it is done.

The census is also larger than the first version said: five witnesses inside the
production module, a separate witness file, two host_standup prose strings, five
plan documents, and DESIGN section 2 (fixed separately in #8559). Several plan
docs cite types the module no longer has, which its own dissolution receipt
records as removed in #5904 -- the prose was stale before this cut.

AND MY ENDPOINT FINDING WAS WRONG. I recorded that grounding_lens_test cites
product.compute_fabric.Endpoint as a fabricated citation of the section 3 class.
It is a controlled fixture: two hand-built concept_decl rows testing that a
product.* namespace is not layer-excluded, and the sibling row names
product.network_topology.Url, which also does not exist. The test needs a string
shaped like a product qualified name, not one that resolves, and an
independently authored fixture is exactly what DESIGN section 5 endorses. I read
a fixture as a citation because the string looked like one.

Recorded rather than quietly removed. A plan that silently drops its own false
claims teaches nothing, and this is the same adjacent-question error the
document charges elsewhere.

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

* Cut B: the census claim was false, the method is the finding, and Shape moves to product.fabric.work

I asserted that every reference to product.compute_fabric is a string literal,
so the deletion refuses nothing. False. host_standup_spine_witness_test calls
the module's own witness_fabric_binds_need_to_opportunity by bare name, and so
does compute_fabric_resource_witness_test; both predate the base I measured.

The method is the real finding. I grepped the module name, and a bare call
carries no module name, so that grep could never have found it. A .dag
reference takes at least three forms and I checked one, then reported the
output as a structural fact. Caught by the implementer, not by me.

#8413 also merged mid-cut, adding three real imports of Shape from
product.fabric. Ruling: Shape and HardRequirements move into
product.fabric.work and the rest of compute_fabric is deleted. The dead
module's only load-bearing content is Shape, and a replacement migration moves
the surviving authority to its owner rather than finding a substitute so the
old file can die intact. supply.dag already imports work, so the edge exists
and nothing is minted -- which matters because my earlier brief named
ExecutionClassRef and ResourceSupplyRef as the alternatives and neither exists
on main. Naming unavailable carriers is how a brief pushes someone into minting
one inside a deletion.

Also records the thread-floor coverage obligation that must outlive the cut,
and keeps the earlier Endpoint correction. Two false findings in one section,
both about whether a string was a reference.

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

* Cut C rewritten after measurement: the seam exists, and the live workflow emitter bypasses it

I specified Cut C as severing a wrong edge -- the customer-facing execution
class depending on physical supply -- and said the seam needed building. Both
halves were wrong.

gunbc.ci_runner_target already exists, already is the authority, and already
projects the runs-on spec and the memory regime from one selection. Its
FleetSelfHosted arm delegating to runner_spec_from_offer is correct and
deliberate: a self-hosted fleet runner's labels genuinely are facts about the
physical fleet.

The real defect is narrower. witness_floor_workflow reads gunbc_ci_runner_spec()
-- the un-seamed function -- while the seam's own projection
gunbc_ci_selected_runner_spec() has no workflow consumer at all. The module's
note says "ci_workflow reads the spec projection now", but ci_workflow was
deleted in the floor cut, so the bypass is most likely collateral of that cut
rather than an original decision.

It is a provably safe change: ci_runner_target_witness_test already asserts the
two specs are equal, so the emission is byte-identical and that witness is the
control. What it buys is that runner selection becomes a one-row edit again --
the whole reason the seam was built -- and one of three direct
runner_spec_from_offer call sites disappears ahead of Cut D. The other two are
legitimately about physical placement and stay.

Also records the stale present-tense note inside ci_runner_target itself, and
the Cut D census measured by declaration name rather than module name.

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

* Cut D is not a type swap: Offer deliberately carries no physical facts, and the label derivation reads them

Measured the two carriers rather than assuming they were the same shape, since
the last three specifications here were wrong from under-measuring.

ComputeOffer carries ComputeSupplyFacts -- a ComputeHost, an ExecutionSurface,
observed performance. Offer<P> carries none of that: id, executor, a one-axis
Shape, capabilities, trust domain, quantity bound, quote, evidence. The absence
is deliberate and consistent with the module's own reasoning about
remaining_capacity: an offer states what is offered and on what terms, and the
physical realization behind it belongs to another layer.

So runner_spec_from_offer's reads of offer.supply.physical and
offer.supply.execution.os have no counterpart in the replacement, and "migrate
the consumers to Offer" is under-specified in the way that pushes an
implementer into either widening Offer with physical fields -- fusing
realization into the offer, exactly what its shape avoids -- or quietly
dropping the derivation.

Records the three honest options and picks none unilaterally. Also strengthens
the ordering argument: Cut C removes the CI interface from Cut D's population,
so the physical-facts question gets answered once at the runner-target seam
rather than once per consumer mid-migration. Cut D is not dispatchable until
one option is signed off.

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

* Cut D signed off: decompose the fused carrier rather than replace it, and all three of my options were wrong as stated

The fork came back refusing every option in the form I put it. The construction
is a combination: option 1's severance but with ExecutionClass rather than
ComputeHost as the destination, option 3's relation as a provider-owned
OfferBacking rather than a field on Offer, and option 2 rejected outright.

Option 1 was right that runner_spec_from_offer reads no market fact, and wrong
about where the labels come from: runner labels describe the environment
presented to the job, not the bare-metal host, so ComputeHost -> RunnerSpec
still points the wrong way. Option 2 would collapse selected execution class
into selected physical backing, making the customer-facing target change
whenever placement changes. Option 3's relation is necessary but is not
one-to-one, so a single field would make quoted supply unrepresentable.

Severing does not lose the offered-versus-ran link: it is proven at offer
publication, grant issuance and realization readback, and the severance removes
the relation from the wrong consumer rather than removing the relation.

Verified the observed_performance ruling rather than taking it, and it is
stronger than stated: one declaration, five row initializers all empty, one
plan mention. No consumer and no data.

Records Cut D as a five-way consumer classification in four steps, and adds a
fourth acceptance condition to Cut C -- the old direct projection is marked for
deletion with D rather than surviving as a permanent equal path, which is what
keeps C from creating the interval it exists to close.

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

* Cut C's carve-out gives Cut D a precondition: D4 cannot delete runner_spec_from_offer yet

Condition 2 is ruled to read "no CI workflow emitter", not "no production
emitter". fleet_converge_workflow calls gunbc_ci_runner_spec directly and must
not be routed through the CI seam: selected_ci_runner_target is a one-row
function built to be flipped to a cloud runner, and fleet convergence must run
on the fleet, so a flip would send the converger to a machine that cannot reach
the hosts it converges. Which machine runs CI and which machine converges the
fleet are two questions with the same answer today and not the same question --
FleetSelfHosted there is a coincidence of value, and merging the authorities
would be nicknaming in reverse.

That carve-out has a consequence neither the sign-off nor I had seen: D4's
deletion list includes runner_spec_from_offer, gunbc_ci_runner_spec lives in it,
and fleet_converge_workflow still consumes it. So D4 cannot execute as written.
Fleet convergence needs its own fleet-derived runner authority first, and the
deletion is conditional on it.

Recorded now rather than at D4, because a deletion list that cannot execute
surfaces as an implementer improvising a merge of two authorities to make the
cut compile -- the arm this program has already had to stop twice.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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