Skip to content

First production CI binding: the fabric authorizes and produces the witness run - #8576

Merged
briansrls merged 25 commits into
mainfrom
fabric/ci-replacement
Aug 20, 2026
Merged

briansrls merged 25 commits into
mainfrom
fabric/ci-replacement

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

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:

witness asserts
attempt_for_different_work_refuses wrong Work refused before any resource question
foreign_trust_domain_refuses_despite_ample_capacity 1024 threads does not buy trust
missing_capabilities_refuse / insufficient_threads_refuse capability and shape walls
fleet_offer_authorizes_the_floor positive control — the wall is not a brick
larger_offer_still_authorizes the shape wall is an inequality, not an equality
floor_argv_carries_every_declared_source_root join + exact arity, proven discriminating
fabric_argv_and_workflow_step_agree_on_source_roots fabric and live workflow are two projections of one authority
floor_contract_keeps_the_parse_gate_as_its_own_step the v1 parse gate stays separately receipted

Why 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

  • review 53848 was right twice: a comment claimed a derivation that did not exist, and the argv was a string blob where extdeps.exec.command.ArgvCommand already existed.
  • I then declared a substrate gap that is not one — flatten and list destructuring are genuinely absent, but fold is present and is the required primitive. The argv is now a total fold.
  • witness_layer_roots was already the source-root authority; my own list was a fork of it.
  • Local gunbc run --entry green is not evidence of floor admissibility — different scope construction. Two resolution errors reached CI that way.

Brian Searls and others added 10 commits August 19, 2026 21:07
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
@gunbai-bot gunbai-bot Bot changed the title Plan: replace GitHub Actions with the fabric, as one atomic cutover First production CI binding: the fabric authorizes and produces the witness run Aug 19, 2026
Brian Searls and others added 4 commits August 19, 2026 23:17
…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
@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

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 block

The premise is that a claim file with no import statements cannot resolve. It does not hold in this repo. Claim files resolve under the floor's prepared subject rather than their own import closure, and import-free claim files are the established pattern rather than an anomaly:

dag/test/claim files: 835 ; with ZERO import lines: 231

The two siblings sampled (access_authorization_profile_witness_test, altra_contact_map_witness_test) do import — 4 and 5 lines respectively — but they are not the convention.

CI is the receipt, not my say-so. Run 32312645914: planned=9724 executed=9724 terminal=9724 passed=9417 failed=1. The floor planned and executed this file's witnesses. Had the module failed to resolve, the floor would have refused during strict preparation with nothing downstream running — which is exactly what happened two pushes earlier, when this file really did carry two unresolvable symbols.

I want to be precise about what that does and does not settle, because I got this wrong earlier in the branch: local gunbc run --entry green is not evidence of floor admissibility (different scope construction). Only CI settles it. It has now settled it.

Finding 2 — ArtifactManifestRef return type

Correct to flag, and worth confirming rather than assuming. ArtifactManifestRef is NonEmptyStr where brand("ArtifactManifestRef"), and brand refinements are unenforced at compile time in this substrate — the predicate is deferred. So concat(...) at that return site resolves and runs, proven by execution.

The honest reading is not "no issue" but: the type reads as a wall and is not one. That is a real observation about the fabric's ref carriers rather than a defect in this diff, and it is the same class DESIGN §4b names — a richer type name is not safety until construction enforces the distinction.

The defect neither of us named

floor_architecture() was declared with zero consumers. v2.lens.inert_carrier caught it on CI as an unrostered inert carrier, while main was green — so I checked against main's own runs before attributing it, and it was mine.

The part worth recording: I authored it in the same commit whose message refused to add an unread architecture field to Offer, citing review 53848's declared-but-nothing-derives-it defect. Refusing that defect on one side of a module boundary and committing it on the other is the same defect. The lens read the diff better than I did. Removed; the architecture decision stays in prose and the plan until it has a consumer.

— sent from silent-bear-842

Brian Searls and others added 11 commits August 20, 2026 00:17
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
@briansrls
briansrls merged commit 3a3dc83 into main Aug 20, 2026
1 check passed
@briansrls
briansrls deleted the fabric/ci-replacement branch August 20, 2026 02:58
briansrls pushed a commit that referenced this pull request Aug 21, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant