Repository navigation
First production CI binding: the fabric authorizes and produces the witness run - #8576
Conversation
Operator direction 2026-08-19: do the migration in one PR, no shadow process -- which is the replacement doctrine's default rather than the gap-intolerant carve-out I had proposed, so the plan is written to that. Frames GitHub Actions as a fused authority -- event source, allocator, executor, status sink, log store, concurrency -- and claims only the two the fabric has authority over. Measures the contract to preserve from the live workflow rather than from memory, and calls out the two refusals most likely to be lost silently: the v1 parse gate as a separate step, and cancellation being asymmetric between PRs and main. Argues this is the fabric's first production consumer and the forcing function for Cut D, whose two blocked preconditions are exactly what a CI replacement must construct anyway. States the bootstrap hazard plainly rather than mitigating it with optimism, and names what gets weaker: control-plane independence, log retention, and re-implemented concurrency. Four questions left open for sign-off rather than decided by the author. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…eturns Both confirmed against the plan as written; one is mine outright. workflow_dispatch is listed in the preserved-contract table as must-survive and the designed loop has no manual trigger at all, so the plan enumerates the contract correctly and then specifies something that cannot satisfy a row of it. That is worse than omitting the row, because the table is what a reader checks the design against. Manual re-run is also not a convenience -- it is how a human recovers from an infrastructure flake without pushing an empty commit -- and in fabric terms workflow_dispatch IS a Demand authored by a person. Branch-head polling is a sampling of a mutable pointer, so two pushes between polls collapse into one observation and the intermediate commit silently gets no check at all. That is the empty-observation narrow through a different door: I sampled a pointer and saw one value, rendered as there was one commit. The correct construction is reconciliation against durable state -- desired check targets differenced against observed check runs -- which is the shape the fleet spine already uses. A reconciler that misses ten minutes converges; a poller that misses ten minutes has lost the events. Also keeps the reviewer's framing of the risk: whether polling preserves every refusal without becoming a new ambient authority. A poller acts without being asked, which in fabric vocabulary is a Demand with no authenticated author. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
The PR added docs/plans/fabric-ci-replacement.md with no HandAuthoredDocBind row, which the sibling recut-program doc has and this one silently did not. An unbound plan document is outside the cited-symbol population, so a rename or deletion of any system it discusses cannot red -- which is the exact gap gunbc.documentary_refs exists to close, reintroduced by me one document over. Neither the author nor the review caught it: the review checked for substrate, unit-modeling and hand-Rust violations and correctly found none, because an orphan is an absence and a diff review looks at what is present. Binds four subjects at subject grain. witness_floor_job is deliberate: it is the thing being replaced, so if it is renamed or restructured before the cutover this document reds rather than quietly describing a job that no longer exists. All four verified to resolve before committing, since a bind row citing a fabricated symbol is the defect it exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…poch fences nothing on its own Both confirmed, both mine. PR CI does not test the branch. actions/checkout on a pull_request event checks out refs/pull/N/merge, a commit GitHub constructs by merging head into base, so the real subject is the branch AS MERGED. My plan derives Work from a polled commit's tree, which for PRs is the head tree -- a different and strictly weaker subject, under which a PR green in isolation but broken when combined with main would pass. That is a refusal the current CI enforces and the replacement as drawn would drop. I had this written down. My own note on comparing CI runs says to check the merge subject and not just head; I applied it to comparing runs and not to defining the Work. Consequences now carried: Work identity depends on two commits so it moves when main moves, we must construct the merge ourselves and a conflict is a typed refusal rather than an absent check, and fetch-depth 0 is load-bearing. LeaseEpoch alone fences nothing. A value does not exclude anyone: two reconcilers can both read epoch N and both issue a grant. Fencing needs a durable compare-and-swap where exactly one writer observes success and the loser refuses. Cut A extracted the epoch as an immutable coordinate and never claimed to provide the transition; I read the extraction as though it had, which is naming a carrier and treating the name as the guarantee. That gives the plan a prerequisite it did not have -- durable state with an atomic transition, the first stateful infrastructure in the proposal, named rather than absorbed into 'the reconciler keeps track'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…rections and 16 conditions Also refutes the sign-off's most consequential factual claim. It states main has no enforced required check and builds a failure-mode argument on it. Measured: the branch-protection endpoint 403s, but the RULESET API shows an ACTIVE ruleset requiring exactly the context 'witnesses', plus deletion and non_fast_forward, on the default branch. Reading empty branch protection as an unprotected branch is the nearby-question failure. Three consequences. Deleting witnesses.yml without addressing the ruleset makes every PR permanently unmergeable including the rollback PR, which section 6's rollback story assumed a human could merge. The required check carries NO integration_id pin, so any source publishing a check named 'witnesses' satisfies it -- keeping the context name makes the gate transition a no-op with no window where the gate is absent, which is now a design constraint rather than a preference. And the fail-closed failure mode the sign-off wanted is the one we already have, so its premise would have licensed weakening it. Records the eight blocking corrections, including three already registered, and the answers to the four questions: PRs too, off-fleet one-shot control plane, execution class narrowly here, and Work identity over the exact commit rather than the tree with reuse disabled for the cutover. Flags one thing for the operator rather than adopting it: the sign-off reads 'no shadow' as no long-lived dual authority rather than no wet proof, and requires a bounded pre-merge canary. I think that is right and my own pre-merge argument was too thin, but it reinterprets an operator instruction so it goes back to the operator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…-deployed canary Wet proof before merge comes from the PR that implements it -- add a second job, confirm it green, then the same PR deletes the first job and the workflow. That resolves condition 15 and beats both the reviewer's canary and my too-thin pre-merge argument: it re-runs on every push so it cannot rot between proof and merge, needs no separate deployment or intake-disabling step, and its evidence is a green run on a commit in the PR's own history rather than a claim about something that happened on a control node. Verified the phase is gate-neutral rather than assuming it: the live required check on main is named 'witnesses' from the github-actions app and the job id is 'witnesses', so for Actions the context is the job name. A second job publishes its own check under its own name, which is not required and cannot disturb the gate. That also makes the section 11 naming constraint load-bearing in a second way. The gating context IS the job named witnesses, so deleting that job is exactly what removes the gate, and because the required check carries no integration_id pin the fabric's check of the same name satisfies the rule from a different source with no gap. Job B must therefore not be named witnesses during the two-job phase; the fabric claims that name only at the cutover commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
Combining the reviewer's rollback inference with the operator's two-job ruling exposes a blocker in the final commit. A pull_request workflow runs from the PR's merge commit, so the PR that DELETES witnesses.yml produces no job named witnesses, the required context is never reported, and the ruleset holds it at waiting-for-status forever. The property that makes the two-job proof work is the same one that makes the deletion self-blocking. Records three ways out and does not pick one: the fabric publishes witnesses for the cutover PR's own merge subject (elegant -- the cutover is gated by the system it installs, and the ruleset never changes), a tested bypass (which promotes the break-glass drill from prudent to mandatory), or splitting the file deletion into a second PR (safest, in tension with one-motion though the authority still moves once). Flags that I have NOT tested the premise, and that a disposable branch with a temporary ruleset settles it cheaply -- every confident untested structural claim in this program has been wrong at least once today. Also corrects my own section 11 claim. non_fast_forward prevents force pushes, not ordinary fast-forward updates, and deletion prevents deleting the ref; neither bans a normal push to main. The required check is the only operative blocker, which is narrower and more actionable than what I recorded. Rollback now requires the effective-rules endpoint rather than the ruleset list, and bypass_actors fetched with credentials that can see them -- GitHub omits that property from under-privileged callers, so an empty reading is not evidence of no bypass, which is the same shape as the mistake that produced the retracted claim. App pinning deferred to a later hardening cut, with its preconditions and the update-not-recreate constraint recorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…nder a grant First executing slice of the CI replacement, scoped to what the operator asked for -- supplant the witness process with the fabric process running the witnesses -- rather than the whole control plane. The argv that runs the required floor is now PRODUCED BY THE FABRIC from a Work contract and an executor's Offer, instead of being written into a workflow step. No authorization, no command: the grant is not a label beside the run, it is the only thing that can yield one. What is deliberately absent, and why it is not a gap: polling, durable state, the compare-and-swap authority transition, supersession and check publication are the CONTROL plane, and they are only needed once the fabric owns TRIGGERING. While Actions still triggers it still supplies concurrency and supersession, so the largest hole in the plan -- the durable single-writer transition -- is dissolved by the sequencing rather than deferred arbitrarily. Building it now would put a second scheduler beside a live one. Eight witnesses, green by execution, and the refusals are the content: an attempt naming a different Work is refused before any resource question, since authorizing the wrong Work cheaply is still authorizing the wrong Work; a foreign trust domain is refused DESPITE ample capacity, because capacity must not substitute for trust; missing capabilities refuse; and insufficient threads refuse. Two controls stop those from being satisfied by a function that refuses unconditionally -- the fleet offer authorizes, and a LARGER offer still authorizes, because the shape wall is an inequality and one that rejected a bigger machine would be a bug wearing a wall's clothing. The contract keeps the v1 parse gate as its own step rather than folding it into the floor, so a receipt can say which gate failed. That distinction is what gunbc#8466 -> #8519 paid to learn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ng blob Both findings verified and both stand. FINDING 1. floor_run_command carried a comment saying it 'is derived from the contract's own source-root inputs rather than spelled twice', and nothing derived it -- the roots were spelled once as a colon-tagged blob in the contract's inputs and again inside a hand-authored argv string. The comment is the worse half: duplication is visible, a false claim tells a reader not to look. That is the comments-assert-what-the-code-omits class, caught by review and not by me. Now one list is the authority. floor_inputs_manifest_ref folds it, so the two representations the review found cannot drift apart silently. FINDING 2 on the argv being a string blob also stands, and the fix was available: extdeps.exec.command already owns ArgvCommand, so a shell line here was a second representation of how a process is invoked. The command is now that carrier. WHAT COULD NOT BE CONSTRUCTED, named rather than papered over. Each root contributes TWO argv elements because claim_executor has no --flag=value form -- checked against its parser rather than assumed -- so a total derivation needs a flat-map. The substrate has no flatten (zero corpus occurrences) and no list destructuring in match patterns (probed: [h, ...t] is a parse error). So the expansion stays hand-written, declared as a substrate gap with its dissolution trigger, and the residue is closed by an EXECUTING witness rather than a comment. That witness asserts a join, not a count -- a count would pass if someone wrote one root twice, which is the drift the review correctly said a string-to-string comparison could not detect. Proven discriminating: adding a source root without its argv pair returns false, restoring returns true. The probe also re-confirmed a filed defect: a parse error in for_each_parsed_module_binding panics rather than refusing typed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ot authority CI FIX (both errors in one push, since a cycle here is ~12 minutes and the floor refuses at strict preparation so nothing downstream runs). The test file used 'Nat' as a parameter annotation and 'string_list_contains' from std.materialization_ladder; neither resolves in the floor's prepared subject. Replaced with HardwareThreadCount directly and with the 'any' builtin, both verified present in floor-resolved claim files by counting actual usage rather than assuming. The real lesson is that my local green was never evidence. gunbc run --entry resolves a different scope than the floor's prepared subject, so a claim file passing standalone says nothing about floor admissibility -- which my own notes already say and I did not apply. AUTHORITY FORK REMOVED. gunbc.ci_layer_roots witness_layer_roots is the existing source-root authority and the live workflow already folds it into its flags; declaring floor_source_roots here was a fork of it, one level above the duplication review 53848 caught. AND THE SUBSTRATE GAP I DECLARED DOES NOT EXIST. The previous commit hand expanded the argv pairs and registered a dissolution trigger on the grounds that expanding one list element into two needs a flat-map the substrate lacks. flatten and list destructuring are indeed absent -- both measured -- but 'fold' is present, is exactly the primitive required, and the workflow module two files away was already using it for this same job. The argv is now a total fold over witness_layer_roots. I declared a language-layer gap without enumerating the language, which is the failure my own notes name as searching by remembered name instead of reading the authority surface. Per DESIGN 4b(4) the join witness stays enrolled as a regression control rather than retiring with the wall it used to be. Adds the substitution check: the fabric's argv and the live workflow's step agree on the source roots, in order. Deliberately NOT byte equality -- the shell line carries prefixes and quoting that an argv list does not, and asserting those are the same string would assert that a transport never differs from the thing transported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…orkflow replacement From swift-badger-524. The reframe changes what counts as done -- you can successfully replace Actions and still have no saleable CI system -- and two of its four corrections name defects in code already on this branch. Locates a layering defect I authored: authorize_floor_run answers a provider-neutral fungibility question from inside a gunbc module, and FloorRunRefusal is a general fungibility refusal wearing a floor-specific name. Nothing in ShapeNotCovered, TrustDomainMismatch or CapabilitiesNotOffered is about the floor. The gunbc module should keep the floor's contract and requirements and nothing else. Confirms the pricing gap by inspection: authorize_floor_run checks shape, capabilities and trust and considers price nowhere, so gunbc's floor is an always-admitted path -- the privileged arm the arbitrage thesis forbids, because if self-CI bypasses the market the opportunity cost of our own work stops being computable. Records that GitHub vocabulary must not leak into the fabric: main-vs-PR is how this binding obtains desired demands, never three modes inside the fabric. Supersedes the off-fleet placement answer with the real invariant -- no single failure domain removes observation, canonical state, allocation AND the means of restoring them -- plus control work as its own execution class and four prerequisites, including that LeaseEpoch is a fence and replication without CAS makes the race worse. And splits WorkContentKey's three concerns. Verified rather than proposed that the split already has a carrier: SatisfactionRequirement lives on the Demand, so reuse policy rides the demand and identity stays identity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
… the class stops naming an architecture Three corrections from product direction, all accepted, all defects in code I authored on this branch. LAYERING. authorize_floor_run answered a provider-neutral question -- does this executor satisfy these requirements -- from inside a gunbc module, and FloorRunRefusal was a general fungibility refusal wearing a floor-specific name. The fold is now product.fabric.supply offer_eligibility_for and the gunbc module keeps the floor's program and contracts. It takes the budget ceiling as scalars rather than demand.BuyTerms, because supply must not import demand -- a backward edge that module already carries once and must not deepen. PRICING. The floor now clears a price like any other demand. The owned-fleet offer quotes ZERO and still goes through the check: the zero is a supply-side fact, the opportunity cost is a demand-side question, and reading the first as answering the second is exactly what made the always-admitted path invisible. That sentence is on the carrier, where the mistake is available to make. Affordability and capability are separate arms because their remedies differ -- raise the buy order, or find another supplier. ARCHITECTURE. The class spelled its arch inside a capability tag, linux-aarch64-rust-toolchain, making an accident of our own supply look like a property of the execution class. Ampere is what we stock; the product must serve x86 and arm. The arch is now a modeled Architecture from the cited toolchain authority and a fact about the gunbc floor's workload, not a parameter of what a CI class may be. The offer-side architecture field and the fungibility arm reading it land TOGETHER, not now: Offer has 25 constructors in tree, and an unread field would be the same declared-but-nothing-derives-it defect review 53848 caught here once already. One refusal lost its home in the move and is not silently dropped: the attempt-names-a-different-Work check is not a fungibility question and belongs at grant issuance. Recorded rather than deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
Makes 'ordinary priced demand' a row someone can read rather than a property of a fold's signature. Reuse is disabled for the cutover ON THE DEMAND -- terminal_receipt_may_satisfy false, new_attempt_required true -- so an identical Work with an accepted receipt still executes, preserving today's Actions behaviour while the floor's purity as a function of the tree is unproven. Expressed here rather than in the Work key because turning reuse on later must edit a policy row, not edit what a Work IS. The plan's section 13 and the sign-off that answered it both conflated those; the carriers never did. step_reuse_permitted stays true: a materialization provider may accelerate a rerun, which is not the claim that the step completed. A build cache making the second attempt faster is not a receipt. priority_class is documented as not being a privilege escape -- it orders demands that can all be afforded, it does not admit one that cannot. If it ever becomes the field that lets gunbc jump the queue, the opportunity cost of our own work has stopped being computable. Four witnesses, green by execution. The ceiling one is proven discriminating rather than asserted: with the floor's own buy terms at 5 against a quote of 6 it refuses, and raising the ceiling to 7 flips it, so it reads the ceiling rather than always refusing. Also records that the arbitrage runs WITHIN an architecture -- own fleet versus rented ARM at arm64 -- not across architectures. I had promoted the cross-arch pool to the mechanism, which made the arbitrage look nearly empty; it is the second-order term, and the first-order pool is 100% of our floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
CI refused with inert_carrier_no_unrostered_or_stale on my branch while main was green, so the failure was mine rather than inherited -- checked against main's own runs before attributing it, since a cross-run delta should suspect my own head first. The cause: floor_architecture() had zero consumers. I declared it with a comment explaining that the arm reading it would come later, IN THE SAME COMMIT whose message refused to add an unread architecture field to Offer by citing review 53848's declared-but-nothing-derives-it defect. Refusing that defect on one side of a module boundary and authoring it on the other is the same defect, and v2.lens.inert_carrier read the diff better than I did. The architecture decision now lives in prose and in the plan until it has a consumer, which is the honest state: a decision recorded is not a carrier modeled, and minting one early buys nothing but a row that lies about being load-bearing. Also settles review 53880's first finding by measurement rather than argument. Its premise -- a claim file with no import block cannot resolve -- does not hold: 231 of 835 files in dag/test/claim carry zero import lines, because claim files resolve under the floor's prepared subject rather than their own closure. The two siblings it sampled do import; they are not the pattern. CI is the receipt: the floor planned and executed 9724 witnesses including this file's, and the only failure was the inert carrier above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
|
Both findings in review 53880 checked. One is refuted by measurement and CI; the other is correct in its suspicion and harmless in fact. A third defect the review did not name was real, and CI caught it — that one is fixed. Finding 1 — the missing import blockThe premise is that a claim file with no The two siblings sampled ( CI is the receipt, not my say-so. Run I want to be precise about what that does and does not settle, because I got this wrong earlier in the branch: local Finding 2 —
|
Price census: Hetzner Cloud bills a one-hour minimum and rounds partial hours up, so one-fresh-VM-per-job is not viable and execution must multiplex onto long-lived hosts. Two consequences are about this branch rather than about infrastructure. OfferQuote cannot express the rule that changed the design. Every arm is a rate and nothing else -- no minimum quantum, no rounding direction, no minimum charge -- so Hetzner (1h minimum, rounds up), Ubicloud (per-minute, no minimum) and Namespace (1-minute minimum, next 15s rounds down) are indistinguishable in the model and differ by roughly 12x in fact. execution.dag already SAYS the supplier binding owns the quantum, rounding and minimum charge; no such carrier exists. The census turned a theoretical gap into a load-bearing one. And it is a defect in offer_eligibility_for: the affordability arm compares the quote amount against the demand's ceiling, which is the wrong number whenever a minimum billable quantum applies. A five-minute job against an hourly-minimum supplier is charged the full hour, so the fold would admit an offer the demand cannot afford, silently -- worse than refusing, since the arm exists to make an unaffordable offer unselectable. Worth recording now precisely because the first consumer does not exercise it: the own-fleet zero-quote case is honest under the current fold, and the first consumer is the one whose shape hardens. Also records the asymmetry that is the business: we rent by the hour and sell by the minute, so the fabric must carry two billing rules on the two sides of one execution. A model assuming one rule per fabric cannot represent the arbitrage it exists to run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…e margin sentence Verifying the affordability claim against the landed code found a SECOND defect, independent of the minimum quantum and simpler: offer_quote_amount strips the unit off a rate and returns it as a total. The arm then compares it to maximum_buy_order, which demand.dag defines as the ceiling on a single grant. So the fold compares a rate to a total, and compares per-second against per-hour quotes as if they were the same number -- a 3600x error with no refusal. std/measure.dag already separates MoneyRate<PerSecond>, <PerHour> and <Once>; the fold erases the parameter that distinguishes them. Worse than the quantum gap because no new modeling is needed to refuse, only to stop erasing, and it is the section 5 tell exactly: satisfiable while the realization lies. Records acquisition as a THIRD concept beside placement and pricing, per product direction. Every OfferQuote arm assumes an offer is existing capacity we choose among; renting CREATES capacity, so a minimum billable quantum is also a minimum acquisition commitment -- you buy an hour speculatively before knowing whether demand to fill it arrives. Holding idle is loss, releasing early wastes paid time, re-acquiring inside one hour pays twice. Named explicitly so it is not absorbed into placement, which is where it would wrongly land. Also states the margin verbatim: we rent by the hour and sell by the minute. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
… rate to a total offer_eligibility_for compared offer_quote_amount(offer.quote) against maximum_buy_order, which demand.dag defines as the ceiling on a SINGLE GRANT -- a total. offer_quote_amount reads .amount off whichever arm, so it strips the unit off MoneyRate<PerSecond> and MoneyRate<PerHour> and hands back a bare scalar. The fold therefore compared a rate to a total, and compared per-second and per-hour quotes against the same ceiling as if they were the same number: a 3600x error admitted in silence. std/measure.dag already carried the distinction; the fold erased the type parameter that made it. The affordability arm now matches the quote. QuotedFlatPerGrant is a per-grant total and compares as before; the two rate arms refuse with RateQuoteNotPriceableAgainstGrantCeiling carrying the amount they could not price. That is a refusal and not a widen -- the frequency of being offered a rate we cannot yet price is countable rather than absorbed into OfferEligible -- and it dissolves when the offer carries its billing rule (quantum, rounding, minimum charge) and the demand carries an expected duration, at which point pricing a rate is total and the variant has nothing to refuse. EVIDENCE, executed both ways rather than argued. Against the old comparison with the variant kept so the tests still resolve: an_hourly_rate_is_not_priceable_against_a_per_grant_ceiling false per_second_and_per_hour_quotes_both_refuse... false a_per_grant_total_under_the_ceiling_is_still_eligible true Against the new arm all three return true, as do the pre-existing controls fleet_offer_is_eligible_for_the_floor and floor_buy_terms_ceiling_is_the_one_eligibility_reads. The positive control holding true in BOTH directions is what distinguishes a specific refusal from a fold broken into refusing everything, which would have passed both reds. Found by verifying a claim already sent upstream, not by review: review 53896 approved this arm as clean, citing it by name as having typed diagnostics and no absorbing fallback, while it was comparing a rate to a total. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
The section stated the defect and promised a fix; it now records the fix that landed, with the discriminating pair executed against both the old and the new arm and the positive control holding in both directions -- which is what separates a specific refusal from a fold broken into refusing everything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
Product ruling: an offer carries a marginal cash cost and an opportunity cost, and SELECTION READS THE SECOND. An owned hour entering selection at 0.00 outbids every paying customer, so self-CI would always win and the arbitrage would be defeated by the mechanism meant to run it. The cut applies uniformly -- a rented host inside its paid hour has the same shape as an owned one -- which is the tell it is right. The first consumer encoded the forbidden shape. t_fleet_offer quoted the owned fleet at 0 and fleet_offer_is_eligible_for_the_floor asserted it clears, while the comment directly above it said a zero quote is not an exemption from the price check. The comment stated the principle the fixture violated -- the same shape as the dimensional defect two commits ago, and again the prose was right and the value was wrong. The fleet offer is now quoted at its opportunity cost, with two witnesses: an_owned_hour_priced_at_opportunity_cost_can_be_outbid, and its control an_owned_hour_is_eligible_to_a_demand_that_meets_its_price so the refusal is a price refusal rather than the offer having become ineligible outright. The first was UNWRITABLE while the fleet quoted zero -- at zero it clears every ceiling, so no ceiling can refuse it and the arm has no discriminating input. The wrong price did not merely misprice, it erased the test. Also corrects supply.dag's note, which said the opportunity cost is a demand-side question. It is a fact about the supply, carried on the offer as its second cost. The note found the right hazard and put the remedy in the wrong layer; corrected rather than reworded because the sentence was cited in a commit message and upstream. OfferQuote is still a single scalar, so nothing forces it to be the opportunity cost rather than the cash cost. That gap, plus lifetime and the time-indexed step function that makes an already-paid hour free and therefore schedulable, is recorded as section 21. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
… fact The previous commit's message said this note was corrected and only the plan was edited. The note itself said the zero is a supply-side fact while the opportunity cost is a DEMAND-SIDE QUESTION. It named the right hazard and homed the remedy wrong: what an hour could have been sold for is a fact about the SUPPLY, carried on the offer beside its cash cost. Rewritten to state the two costs, that selection reads the second, and why the cut applies uniformly -- a rented host inside its paid hour has the same shape as an owned one, so nothing special-cases ownership. Corrected in place rather than reworded because the sentence was cited downstream, and states plainly that the carrier is NOT here yet: OfferQuote is a single scalar, so the obligation to make that number the opportunity cost sits on whoever constructs the offer until the two-arm cost and lifetime land. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
The absorbing-extreme tell belongs in the plan as a REVIEWER'S QUESTION rather than only in the commit that fixed it: with the fixtures as they stand, can a witness be written that makes this arm fire? If not, the arm is untested however many witnesses reference it. Zero was the instance; the class is any extreme absorbing one side of a comparison -- unbounded ceiling, empty roster, top budget. Nothing reports it, because a deleted test cannot fail. Under it, per the meta review, three defects on this branch are ONE defect in three modules: a richer-named carrier standing where a structural guarantee was needed. LeaseEpoch as a fence, FloorRunRefusal homed in gunbc, and offer_quote_amount erasing the unit that separates a rate from a total. DESIGN.md 4b already states the principle; what is missing is its reader. The response to a third instance is to promote the class, not fix a fourth site. It is a measured review gap rather than a suspicion: three times the prose stated the principle while the value violated it, and review approved all three. None is visible at the name-and-shape layer, which is what structural review reads. The lens lands BETWEEN this PR and the cutover, because durable single-writer CAS is the exact next place it bites -- a CasToken type name standing where a durable compare-and-set was needed. Also corrects a merge-state claim of mine. dashboard-ops reviews 8576 reads 8 approve verdicts from 1 DISTINCT PROVIDER, so the two-provider rule is unmet at 1/2 with checks pending. Repeated approvals from one provider do not compose, and I reported the verdict count as though they did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
The general form -- a name promises more than the construction delivers -- is NOT decidable: the promise lives in a reader's head, not in the Node tree. Scoped that way the lens is an unbounded project that will not land between two PRs, and the class sits unguarded while it is attempted. That is section 5's never trap, and I had taken the undecidable scoping. The correction that matters is to my own tell. It says an arm is UNFIREABLE, which no mechanism can measure -- whether an arm could fire under some unwritten fixture is undecidable. Whether it DID fire under the corpus we run is a measurement. Different claims, only the second checkable, so the lens asserts the second: no witness in the floor run ever constructed this refusal variant. That is coproduct-variant observation coverage, decidable by execution, no intent inference, catching the class from the evidence side rather than the naming side. It reports untested, never unfireable; conflating them would put the undecidable claim back inside the mechanism meant to avoid it. Rejects the guarantee-word vocabulary candidate. It is decidable and cheap and it is grep by another name -- section 6 enforces with lenses, not greps -- and it fires on the naming layer we just established is where the promise is not legible. An honest LeaseEpoch and a lying one are spelled identically. Also records that the lens is the backstop and not the proof: the CAS site needs its own two-way executed control regardless, because a lens reporting which arms went unobserved cannot establish the observed ones are correct. And states why CAS is urgent -- it fails as a lost update rather than a wrong number, and a lost update cannot be detected after the fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…n narrow The distinction was written as a freestanding caution. It is an instance of a failure mode DESIGN.md already lists: bottom-as-answer conflated with bottom-as-ignorance, the empty-observation narrow, one level up and pointed at our own evidence rather than at a diff. "I did not observe it" rendered as "it cannot happen" is the same conflation as "I could not compute what changed" rendered as "nothing is affected", and it is worse than the widen for the same reason: a widen is expensive, a narrow is silently uncovered. Section 3 prefers citing the framework that exists to minting a parallel one, and grounding it this way makes the rule memorable rather than local to this lens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
zero_budget_still_clears_a_zero_quote called t_fleet_offer() with a ceiling of 0 and asserted eligible, under a comment reading "free capacity is affordable to a zero budget". That was true while the fleet fixture quoted zero and became false the moment the previous commit repriced it to 40. Confirmed red by execution. I changed the fixture and did not reread the witnesses depending on its old value, and the witness named that old value in its own function name. It is the mirror of the tell written up one commit earlier, and the review question is different in each direction. The erased test asks whether any witness can make an arm fire. The stale premise is a rule about editing: after changing a fixture, reread every witness naming the old value in its name or its comment. Both directions are now in the plan. Repaired with its own t_free_offer fixture, because the property under test is the strictness of the ceiling comparison and never had anything to do with the fleet, plus one_micro_over_a_zero_ceiling_refuses as the control. Also withdraws the economic framing rather than defending it. Opportunity cost is NOT a field on the offer -- what an hour could otherwise have earned depends on which other demands could use it, so it belongs to the assignment evaluation and the decision receipt. And marginal cash is not zero for an owned host: power and cooling are incurred by the act of running, so only the historical purchase is sunk. supply.dag now says the fold screens affordability and would be a lie as a selector, names the quote an asking price, and enumerates what selection would need including the no-feasible-alternative-use and unread arms that keep unknown from silently becoming zero. The two witnesses naming opportunity cost are renamed to what they actually test. The proof that no economic selector exists here: rate quotes are refused for want of a demand duration, and no fold chooses among multiple eligible offers. One scalar standing for marginal cash, supplier price and opportunity cost distinguished only by comments is the richer-name-where-a-guarantee-was-needed class again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
First completed floor run on this branch -- nineteen prior runs were cancelled by the next push before finishing, three failed, none completed. It reported planned=9747 executed=9747 terminal=9747 passed=9440 known_red_held=306 failed=1, with the single failure being inert_carrier_no_unrostered_or_stale. Main was green at the same hour, so the failure is this branch's. Localised by running the lens's two halves rather than guessing: unrostered=0, stale=1, so a rostered carrier had become live. It is Offer, rostered with the reason "declared ahead of the grant issuance that matches demand against it, and referenced only by fabric_terminal_contract_witness_test so far", under the dissolution condition "a live consumer reads the carrier; the stale-roster check then forces this row's deletion". This PR is that consumer -- offer_eligibility_for reads offer.trust_domain, .capabilities, .shape and .quote. So the red is a CLIMB rather than a regression: a carrier left the inert roster because something started reading it, and the lens refused to let the stale row outlive its own condition. Row deleted; unrostered=0, stale=0, witness true, all verified by execution. That also makes this the strongest evidence in the plan that the fabric acquired a real consumer, and better than any assertion the PR could make about itself: an independent lens written before this work measured that Offer stopped being inert. Batched into one push rather than three, per the cadence finding. Also records: that a witness whose name encodes a fixture value will lie the moment the fixture moves, the name being a second copy of the fixture; the floor numbers and the conclusion-histogram detection for the cancelled-run class; and the VacuousArm finding in v2.lens.coverage -- a 13-variant failure-mode taxonomy with no detector, whose near_miss_vacuous_node fixture is authored and never read -- recorded with its receipt and deliberately NOT fixed here, since the taxonomy exists while the detector and observation plumbing do not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…ding it (#8629) * The emitted floor step renders the fabric's command instead of rebuilding it #8576 landed floor_run_command, an ArgvCommand the fabric authorizes, and left gunbc.witness_floor_workflow building the SAME invocation a second time -- its own binary path, its own --required-floor, its own --source-root spelling, folded over the same witness_layer_roots. Then it pinned the two together with fabric_argv_and_workflow_step_agree_on_source_roots, a witness asserting they agreed on source roots. That was validation standing where construction was available, and I wrote it. It could stay green over a drifted binary path, a drifted flag spelling, or a different argument ORDER, because agreeing on the roots is not agreeing on the command. The fork is dissolved rather than re-pinned: witness_floor_run_script now renders floor_run_command through extdeps.exec.command shell_command_render, which already existed and is already the modeled realization for argv-to-shell. WHY THE PATHS BECAME RELATIVE. shell_command_render single-quotes every word, cited to IEEE 1003.1-2017 section 2.2.2 -- correct for an argv and fatal for a word carrying a shell variable, since '$ROOT/dag' does not expand. So $ROOT is not in the argv: the script cds to the root first and the argv stays genuinely an argv, which is what lets a modeled renderer quote it at all. The sibling v1-parse step in this same workflow already uses cd "$ROOT", so this is that step's pattern rather than a new one. Same binary, same flags, same roots, resolved against the same directory -- and the emitted diff is exactly two lines, which is the evidence that nothing else moved. EVIDENCE, executed both ways. Against the old hand-built script: workflow_step_renders_the_fabric_command_and_does_not_respell_it false -> true every_declared_source_root_reaches_the_emitted_step false -> true floor_argv_carries_every_declared_source_root true -> true The third holding in both directions is what separates a specific red from a change that broke everything. The discriminating clause is the second one rather than the first: '$ROOT/dag' was the old fork's exact spelling and is UNRENDERABLE from an argv, so its presence could only mean someone hand-built the path back into the script -- the only way the fork returns. Asserting the render alone would restate the implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Decouple the consolidation witness from one spelling of the invocation #8647's w_ci_invokes_one_composed_mode_not_a_step_ladder matched `claim_executor" --required-ci`, where the trailing double quote is the tail of the hand-built "$ROOT/target/release/claim_executor". This PR renders the step from the fabric's ArgvCommand via shell_command_render, which single-quotes every word per IEEE 1003.1-2017, so the emitted text is now 'target/release/claim_executor' '--required-ci' and the positive clause stopped matching. That red was correct and is what surfaced this. Re-spelling the pattern for the new renderer would have greened the positive clause and left the three NEGATIVE clauses carrying the identical coupling: a --required-floor step growing back emits as '--required-floor', which `claim_executor" --required-floor` cannot match, so all three would have gone permanently, vacuously green while still reading as coverage. The positive clause fails loudly; the negatives fail silently. Matching the flag tokens alone is spelling-independent and strictly stronger as a negative, since it catches a retired invocation under any quoting. Evidence, by execution, not by typecheck: patched, unmutated -> returned true floor_run_command flag mutated to --required-floor -> returned false file restored, byte-identical to backup The mutation control is the point: the defect being repaired is a clause that is green because it can no longer match anything, and the original clauses could not have gone red under any re-spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf * Narrow the consolidation witness's stated claim to what it proves The revised row asserts contains("claim_executor") && contains("--required-ci") as two INDEPENDENT substring checks, and the heading claimed they establish that one run step reaches the binary and names the composed mode. They do not join: `claim_executor` already appears in the build step as a binary name, so the row would stay green if the run step were replaced by something unrelated that merely contained `--required-ci`. The implementation is still grounded -- fabric_witness_run_test proves the exact rendering of floor_run_command reaches the step, and separately that the command selects --required-ci -- so the combined evidence is sound. What was wrong was the comment, which asserted a join the code omits. Narrowed rather than strengthened, one authority per proposition: this row owns "no retired external invocation returned" and nothing else, and names the two rows that own the rest. Raised in side-chat review, 2026-08-20. 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>
Plan doc + first executing slice. The plan's four open questions are closed (§13), and it carries eight blocking corrections from sign-off (§12) plus a self-blocking-cutover trap (§15). This body previously said "awaiting sign-off on four open questions" long after they were answered — that staleness cost a reviewer real work, and it is the same present-tense-claim class the plan itself charges elsewhere.
What executes today
gunbc.fabric_witness_run— the argv that runs the required floor is produced by the fabric under an authorization, from a Work contract and an executor's Offer, rather than written into a workflow step. No authorization, no command.Twelve witnesses, green by execution. The refusals are the content:
attempt_for_different_work_refusesforeign_trust_domain_refuses_despite_ample_capacitymissing_capabilities_refuse/insufficient_threads_refusefleet_offer_authorizes_the_floorlarger_offer_still_authorizesfloor_argv_carries_every_declared_source_rootfabric_argv_and_workflow_step_agree_on_source_rootsfloor_contract_keeps_the_parse_gate_as_its_own_stepWhy the plan's largest hole is absent rather than deferred
The durable single-writer/CAS transition is only needed once the fabric owns triggering. While Actions still triggers, it still supplies concurrency and supersession. Building CAS now would put a second scheduler beside a live one.
Corrections carried in-branch
extdeps.exec.command.ArgvCommandalready existed.flattenand list destructuring are genuinely absent, butfoldis present and is the required primitive. The argv is now a total fold.witness_layer_rootswas already the source-root authority; my own list was a fork of it.gunbc run --entrygreen is not evidence of floor admissibility — different scope construction. Two resolution errors reached CI that way.