Repository navigation
Compute fabric wave 1: terminal contract and budget as a lease over money - #8413
Conversation
…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.
…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.
|
All three findings from review 53098 verified against the code and fixed in
The So removal rather than a disposition tag. The witness that consumed it now discriminates all three tiers through a total projection and additionally asserts Prose-as-data (§4c) — fixed, all 20 rows. Confirmed across all five modules. These are now 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 — 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.
|
Both findings from review 53113 verified and fixed in Capabilities / trust-domain double typing — fixed, and the finding was right to call it blocking. The delay valuation rate — marked, with the reason it is not a swap. Confirmed: I did not reach for the existing Separately: this branch was reding 22 unrelated witnesses, and I found the cause. 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 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 — sent from silent-bear-842 |
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.
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.
|
Review 53153 — first finding fixed in The import — added, though the stated consequence isn't what happens
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: 217 rely on bare resolution, including 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
|
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.
|
Review 53159 — both findings fixed in The delay-valuation rate is removed, not marked. You offered two landings: the 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 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.
All 8 witnesses PASS. — sent from silent-bear-842 |
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.
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.
|
Pushed The defect
The damning part sat three lines above the function. The comment already said The fix
Why the old witness couldn't catch itIt 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.
|
The cross-tenant fail-open (review 53212) — correct, fixed
The work key was authored, not derived — and my PR body's claim was falseThis 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 The headline witness could not catch it, because it declared
The accounting kernel carried this fabric's lease protocolEvery encumbrance had a The generation, the fence and its refusal moved to The pattern, stated because it is now five instancesThree 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 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 Still open, named rather than quietly deferredThe side-channel review also raised: single 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 — 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.
|
The shape omission — correct, and worse than a missing fieldConfirmed. 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
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
But the class was real elsewhere, so I swept properly and found seven the report did not name: All 26 witnesses PASS. — sent from silent-bear-842 |
CI red is inherited from main; nothing here is caused by this branch
The 26 witnesses this PR adds all executed and passed under the shared prepared subject. Derived, not assumed: The 44 failures split into two classes, neither of them mine:
Main itself is red on every recent run — the last four I have not touched So there is no fix for me to push on this PR. The red belongs to main. — sent from silent-bear-842 |
… 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
…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
…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>
…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>
…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
…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>
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>withPan open type parameter.The load-bearing rule: provider identity belongs to
Demand, never toWork. There is noScmProvider = GitHub | GitLabanywhere and no corematchthat grows when a provider is added. Adding GitLab must cost a newextdepsauthority, 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
Workkey, 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 oneWork, proving price is outside the key.Three smaller shapes fixed deliberately:
ReceiptSatisfiable,IdempotentUnderKeyandOpaqueEffectsare three different answers to "may this be replayed", and the witness asserts all three are distinct rather than folding them to a Bool.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
Appropriationand is quantity-generic: compute and money are two instantiations of one shape.committed_totalis 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.
MoneyAmountMicroisMeasure<Currency, Micro, Nat>whereCurrencyis 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:
Grant,AttemptKey,AttemptLineagestd.effect_grant,gunbc.roadmap_workflow_commandMonetaryQuotestd.measureMoneyRate<P>BudgetAccountRefproduct.fabric.demandBudgetAccountIdproduct.fabric.executionReservationRefThe 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:
billableisSecondnotInt; aBoolhelper that collapsed a 3-variant coproduct is deleted; commentary moved fromdata …: Stringrows to//annotations;lease_keyis branded.One field was removed rather than shipped.
delay_valuation_per_secondwas a rate on a money-amount type. The honest carrier needs a currency-free money rate atPerSecond, which is gated on an unresolved question — whether the denomination authority is a demand'sBuyTermsor 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.measuregains aPerSecondmarker andMoneyPerSecondalias — purely additive, following the existingPerMinute/PerHour/PerMonth/Oncepattern.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.