Skip to content

fabric-M0 F0: the durable-completion authority, before the cache - #10641

Merged
briansrls merged 23 commits into
mainfrom
session/warm-moth-142
Sep 8, 2026
Merged

briansrls merged 23 commits into
mainfrom
session/warm-moth-142

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Establishes the fabric-M0 completion gate — the single authority that may call a job successful — before any cache exists. Dispatched brief reordering: a cache accelerates a completion contract and cannot supply one, so building the fast path first produces something that looks like storage without establishing the property M0 actually needs.

What lands

  • gunbc.fabric_m0_completion — admit_job_publication, total over four outcomes.
  • gunbc.fabric_m0_program — F0..F6 as gate predicates.
  • test.claim.fabric_m0_completion_witness — 14 discovered test fn witnesses.

The ordering is load-bearing

The computation verdict is read first and short-circuits, so a successful storage finalization cannot invert a failed build — unreachable, not merely unwritten. Success is the last arm, reached only when every declared obligation and the manifest itself were read back at the origin.

Two constructions rather than validations

Digests are Sha256Digest, not the ContentHash union. std.content_hash makes Fnv1a64Structural a structural fingerprint minted for computation identity, and all three existing ArtifactRequest variants are keyed by it — so structural-fingerprint-as-key is the surrounding idiom, not a hypothetical slip. A union-typed field would let a fingerprint stand where byte integrity is required. Pinning the family at the carrier is the GateRosterHash / v2.std.node.Hash precedent, and leaves the confusion no representation.

ArtifactObligationsDeclared{required: []} is a different constructor from ArtifactObligationsUnreadable. With a bare list, an unreadable declaration is satisfiable by producing nothing — the obligation set shrinking to match whatever arrived. Checked in both directions: declared-empty must still succeed, or the refusal would be satisfied by an authority that rejects every zero-length roster and the distinction would carry no information.

ReadbackUnknown is a third arm. A lost acknowledgement does not establish that a write failed, and an object existing does not establish it is the intended object. Folding unknown into either decided arm is an absorbing fallback: into failure it discards a possibly-committed result and re-executes; into success it greens over bytes nobody has read.

Single authority

The gate roster imports FabricCiGate rather than minting an M0 twin, and carries no status field — matching gunbc.fabric_ci_program, where standing comes only from an executed receipt. M0 stays distinct from the FCI-0..6 required-check migration.

Evidence, executed through claim_batch on the discovered path

14 witnesses PASS. Mutation controls:

Mutation Witnesses broken
baseline / restored 0 (all 14 PASS, source byte-identical)
missing artifact silently satisfied 3
pending arm collapsed into failure exactly 1 — a_lost_acknowledgement_is_pending_not_decided
storage success inverts failed build exactly 1 — a_failed_build_stays_failed_...

The middle row is the one worth attention: collapsing the unknown arm leaves every other conjunct green, because the job still does not publish success. A witness without that specific assertion passes it — and collapsing it looks conservative, which is why it belongs in F2 acceptance explicitly.

Scope honesty

Model grain only. Every OriginReadback here is authored, not observed. This establishes that the authority refuses correctly when handed the facts, and nothing about whether a runner, origin, or teardown path ever hands it those facts. Relatedly, std.materialization_provider records its own readback-provenance gap — observed_digest is an assertion that the transport computed it from the stored parts — so F2 inherits known debt at that seam rather than a guarantee.

F1 is blocked on origin authorization, spend cap, retention and enrolled scope; F3 needs the runner completion boundary. std.durable_compare_and_set is the authority F2 should consume for attempt-bound commit identity.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd

Consumption (DESIGN §3c): the consumer and the route, per declaration group

Answered in the vocabulary §3c asks for. Two groups are consumed by execution in this change; one is a declared frontier with its trigger stated. None is dangling.

1. gunbc.fabric.fabric_m0_completion — the gate (admit_job_publication, OutputObligation, CommittedOutput, OriginObservation, ObligationVerdict, JobPublication).
Consumer: gunbc.fabric.fabric_m0_commit publish_after_commit, in this diff. Route: a direct call — publish_after_commit builds the manifest observation and calls admit_job_publication, and every one of the 42 enrolled witnesses reaches the gate through a real evaluation, not a declaration probe. Executed on this head as standing=planned-and-passed in required-witnesses-floor.

2. gunbc.fabric.fabric_m0_commit — publish_after_commit.
DECLARED FRONTIER. No production route reaches it today; outside the witnesses nothing imports either module, and that is stated in the module itself rather than left to an import census.
TRIGGER, STATED AS THE CAPABILITY: a job-publication path that holds a run's required work and decides its ending — the roadmap's compute-artifact-return-and-materialization capability, where a computation's declared outputs come back identified by content through a manifest the caller reads. When that path exists it calls this function to turn a commit attempt into an ending. Naming a smaller artifact (one caller, one job, one emitted script) would be satisfied while the capability stayed dead, which §4b(3) forbids.
This is a consumption frontier and NOT a §4b(3) rung drop: no rung is lowered by the wait, and the gate's walls are enrolled and executing over the population they are declared for.

3. The two witness modules.
Consumer: the floor, by execution. They exercise the gate's arms rather than asserting declarations exist — each wall is mutation-controlled, and the controls discriminate: removing the work-key comparison reddens only a_resolution_for_another_work_cannot_publish_success; dropping kind from the obligation join reddens only the_same_id_under_another_kind_does_not_discharge_it; the module restores byte-identical.

On +1710 / −0. The zero is correct rather than a tell: this adds a new authority for a question nothing in the corpus previously answered, so there is no predecessor structure to delete. It is not a replacement migration, and no second path is being created beside an existing modelled route.

The frontier is mutual, which is new since this PR opened. gunbc.product.fabric.work on main declares its own §3c frontier on the sealed roster and names THIS gate as its consumer, with the trigger "that gate takes OutputObligationsResolution instead of a caller-supplied coproduct." That condition is met at this head: admit_job_publication takes resolution: OutputObligationsResolution plus judged_work: WorkKey, and no caller-supplied roster parameter survives — every witness fixture goes through resolve_output_obligations. Landing this PR closes a frontier on both sides.

The reciprocal half: this PR CLOSES a frontier main already declared

Stated here so the join is legible in one place rather than reconstructed from two modules.

gunbc.product.fabric.work on main carries, verbatim:

CONSUMPTION FRONTIER (DESIGN §3c): this sealed family is not yet read by a production gate in this closure. Named consumer: gunbc durable-commit admit_job_publication (warm-moth-142 / gunbc#10641). Trigger: that gate takes OutputObligationsResolution (or ResolvedOutputObligations) instead of a caller-supplied coproduct.

The thing that satisfies it is gunbc.fabric.fabric_m0_completion admit_job_publication, whose signature takes resolution: OutputObligationsResolution alongside judged_work: WorkKey. The trigger is met at this head. No caller-supplied roster parameter survives anywhere in the module, and every witness fixture reaches the gate through resolve_output_obligations.

Rung honesty on that claim, path-scoped (§4b(1)). An earlier revision of this body said a hand-built resolution is "unwritable" unqualified. That is inflation, and product.fabric.work names it as such in its own annotation. The seal is structural on the source→.dag acceptance path only — the sole mint is resolve_output_obligations, and constructing the resolved arm by hand is a SoleConstructorViolation with a fixture RED on that carrier's side. The emitted Rust mirror of a sole_constructor type is a public struct with public fields, so a consumer writing Rust against the seed is OUTSIDE the guarantee. Fabric-M0 has no such Rust consumer today, so the rung holds now with a named degradation condition: the first Rust consumer of the seed against this carrier. A class's rung is the minimum across its in-scope paths, so the honest statement is path-scoped, not absolute.

Two consequences a reviewer should not have to derive:

  • This PR is a closure, not an addition. Landing it RETIRES a §3c obligation that main states today, from the side main named. It is not asking to be trusted about a hypothetical future consumer.
  • The seal is not the only wall. The gate also compares the resolution's work_key against the work being judged, so a resolution correctly sealed for a different work cannot publish success — a_resolution_for_another_work_cannot_publish_success is the discriminating control, and removing the comparison reddens only that witness.

gunbc-ci-auto-heal and others added 2 commits September 6, 2026 02:51
M0's five properties are only worth having if "successful" already means the
required result is safely there, so the completion gate is modeled first and
the cache is left for F4. A cache accelerates a completion contract and cannot
supply one; building the fast path first produces something that looks like
storage without establishing the property M0 needs.

admit_job_publication is total over four outcomes and deliberately ordered. The
computation verdict is read first and short-circuits, so a successful storage
finalization cannot invert a failed build -- unreachable rather than merely not
written. Success is the last arm, reached only when every declared obligation
and the manifest itself were read back at the origin.

Two constructions rather than validations. Digests are typed Sha256Digest and
not the ContentHash union: std.content_hash makes Fnv1a64Structural a structural
fingerprint minted for computation identity, and all three existing
ArtifactRequest variants are keyed by it, so a union-typed field would let a
fingerprint stand where byte integrity is required. Pinning the family at the
carrier is the GateRosterHash precedent and leaves the confusion no
representation. And ArtifactObligationsDeclared{required: []} is a different
constructor from ArtifactObligationsUnreadable: with a bare list, an unreadable
declaration is satisfiable by producing nothing, which is the obligation set
shrinking to match whatever arrived.

ReadbackUnknown is a third arm because a lost acknowledgement does not establish
that a write failed. Folding it into either decided arm is an absorbing
fallback: into failure it discards a possibly-committed result, into success it
greens over bytes nobody read.

The gate roster imports FabricCiGate rather than minting an M0 twin, and carries
no status field, matching fabric_ci_program: standing comes only from an
executed receipt. M0 stays distinct from the FCI-0..6 migration.

Evidence, executed through claim_batch on the discovered path: 14 witnesses
PASS. Mutation controls -- missing artifact silently satisfied breaks 3
witnesses; the pending arm collapsed into failure breaks exactly
a_lost_acknowledgement_is_pending_not_decided; storage success inverting a
failed build breaks exactly a_failed_build_stays_failed. Baseline and restored
are all-PASS with the source byte-identical.

Scope: model grain. Every OriginReadback here is authored, not observed, so this
establishes that the authority refuses correctly when handed the facts and
nothing about whether a runner, origin or teardown path hands it those facts.
F1 needs origin authorization; F3 needs the runner completion boundary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…, keep the work list

Four contract gaps found by adversarial review of the first commit, each confirmed
by reading the source it landed.

RECEIPTS DISCARDED THEIR DIGEST. The receipt branch read
ReadbackConfirmed { observed_digest: _ } => ObligationSatisfied, so any confirmed
readback satisfied a required receipt while artifacts got a real comparison -- the
same check, missing one carrier over. CommittedReceipt now carries recorded_digest
and runs the identical verification. This is distinct from the readback-provenance
debt: that asks whether the observation came from the required read, this asks
whether the observed content matches the required object at all.

THE MANIFEST NAMED NOTHING. It carried attempt and digest only, so the contract
established "these objects each passed, and something labelled this attempt's
manifest passed" and never "that manifest names THESE objects at THESE contents".
Independent checks of several objects do not establish the relations between them,
and reconstruction after the worker is gone needs the relation. ManifestMember
makes membership expressible and it is now checked per required object.

SELECTION DROPPED EVIDENCE BEFORE ANY FOLD SAW IT. committed_artifact_for took
filter(...) |> first, so a second conflicting or undetermined row for one identity
was discarded at selection -- and evidence dropped there cannot be recovered by
preserving everything after it. Ambiguity now refuses and names the count.

THE OUTCOME SHORT-CIRCUIT ATE THE EVIDENCE. A failed computation returned before
any obligation was examined, so a job that failed to compile AND had an unresolved
receipt upload reported the compile error and silently dropped the reconciliation
work. The assessment is now computed unconditionally -- a pure fold over supplied
observations, so it performs no read and delays nothing -- and only then does the
computation verdict choose the outcome. Failure dominance is a property of the
result, not of skipping the assessment.

The same information loss existed one branch deeper and is fixed by the same move:
an unreadable artifact roster used to stop the RECEIPT roster being assessed at
all. ObligationUnenumerable is a verdict rather than an early return, so a roster
nobody could list is reported beside the sibling roster's real findings. All four
non-success arms carry the unresolved population.

Evidence, claim_batch on the discovered path: 23 witnesses PASS. Mutations, each
against one captured baseline with the source restored IDENTICAL after: receipt
digest ignored breaks 4; manifest content never compared breaks
a_manifest_naming_the_wrong_content_prevents_success; ambiguity resolved by
position breaks conflicting_rows_for_one_identity_refuse; failed build dropping
the work list breaks a_failed_build_still_reports_an_unresolved_receipt.

STILL OPEN, and not closed by this commit: the function CHECKS supplied
obligations, it does not DERIVE them, so a caller can still author a declared-empty
roster. The gate's independent-derivation requirement remains an input-boundary
obligation for F2/F3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
@gunbai-bot

gunbai-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 6b1fef304ca — four contract gaps found by adversarial review of the first commit, each confirmed by reading the landed source. Flagging because the earlier APPROVE was on 79d0bb62f41, which carried all four.

Gap What the source actually did
Receipts discarded their digest ReadbackConfirmed { observed_digest: _ } => ObligationSatisfied — any confirmed readback satisfied a required receipt, while artifacts got a real comparison
Manifest named nothing carried attempt + digest only, so the contract said "these objects each passed, and something labelled this attempt's manifest passed" — never "that manifest names these objects at these contents"
Selection dropped evidence `filter(...)
Outcome short-circuit ate the evidence a failed computation returned before any obligation was examined, so a job that failed to compile and had an unresolved receipt upload reported the compile error and dropped the reconciliation work

The structural fix for the last one — short-circuit the choice of outcome, never the collection of evidence — also fixed the same loss one branch deeper: an unreadable artifact roster used to stop the receipt roster being assessed at all. ObligationUnenumerable is now a verdict rather than an early return, and all four non-success arms carry the unresolved population.

Evidence, claim_batch on the discovered path — 23 witnesses PASS. Mutations, each against one captured baseline with source restored IDENTICAL after:

Mutation pass/fail Broken witness
baseline / restored 23/0 —
receipt digest ignored 19/4 receipt-content-mismatch + the three work-list tests
manifest content never compared 22/1 a_manifest_naming_the_wrong_content_prevents_success
ambiguity resolved by position 22/1 conflicting_rows_for_one_identity_refuse
failed build drops work list 22/1 a_failed_build_still_reports_an_unresolved_receipt

Two things this does not establish, stated so the green is not read for more than it earns. The function checks supplied obligations, it does not derive them — a caller can still author a declared-empty roster, so the gate's independent-derivation requirement stays an input-boundary obligation for F2/F3. And every OriginReadback here is authored, not observed: this is model grain, and whether a runner or origin ever produces these facts is F2/F3's to establish. std.materialization_provider records its own readback-provenance debt (observed_digest is an assertion about what the transport computed), so F2 inherits that boundary rather than a guarantee.

— sent from warm-moth-142

@gunbai-bot
gunbai-bot Bot marked this pull request as draft September 6, 2026 19:23
gunbc-ci-auto-heal and others added 7 commits September 6, 2026 19:44
…ts commit boundary

THE ROSTER IS DELETED, and unconsumed was the smaller half of the reason. The
roadmap authority already declares this program: gunbc.roadmap.roadmap_authority
carries compute-artifact-return-and-materialization -- "Work that cannot hand back
what it produced has not succeeded" -- whose red controls are the walls this branch
builds (a run reporting success while a declared output is absent refuses; an
output whose content does not match what the manifest claims refuses). A second
declaration of one program under invented F0..F6 names is the DESIGN 3 fork, and
the roadmap row has an owner while the roster had no consumer at all.

gunbc.fabric_m0_commit is the consumer the completion authority lacked, and the
only place allowed to turn what the store said into the readback that authority
takes as given. Deriving it at each call site would put the rule below into every
caller -- a second representation of the arm that costs nothing to get wrong
locally and everything to get wrong once.

THE RULE: a store answer and the ABSENCE of one are different facts.
std.durable_compare_and_set distinguishes committed, precondition-failed and
store-refused, and each is something the store SAID. A write that timed out or
whose acknowledgement was lost obtained none of them, so rendering it as any
CasOutcome fabricates a store verdict to fit the branches available.
CommitTransportOutcome makes StoreAnswered and StoreDidNotAnswer siblings, never
arms of one another, and an unanswered write with the object not yet visible is
PENDING -- collapsing it to absence re-executes work that may already be committed
while looking maximally conservative.

Evidence, claim_batch on the discovered path: 6 commit witnesses PASS, 23
completion witnesses still PASS after the deletion. Mutations against one captured
baseline, source restored IDENTICAL: collapsing an unanswered write to absence
breaks an_unanswered_write_with_no_object_yet_is_pending.

A MUTATION FOUND A COVERAGE HOLE RATHER THAN A WALL, and it is recorded because the
first reading was wrong. Collapsing the unavailable-read arm broke NOTHING, which
is not a passing control but a report that no witness reached that branch: the
existing case exercised StoreAnswered, while the mutation hit StoreDidNotAnswer --
the double-unknown, where the write was never acknowledged AND the confirming read
could not run, which is precisely the case with the strongest pull toward a falsely
conservative collapse. With an_unanswered_write_with_an_unavailable_read_is_pending
added, that same mutation now breaks exactly it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…relation

std.durable_compare_and_set declares this boundary rather than closing it. Its
CasSlotVersion deliberately drops the slot key, and it records the consequence in
its own source: an unkeyed observation describes NO slot, cas_decide never reads
attempt.key, so an observation read from slot B still commits to slot A when the
generations agree. It names the realization as the owner of that relation.
gunbc.fabric_m0_commit is that boundary, so the comparison lives here or nowhere.

The key is DERIVED from the attempt rather than passed beside it. A caller-supplied
key next to a caller-supplied attempt is two spellings of one fact that can
disagree, which is the shape the CAS module itself had to correct once when it
removed its duplicate key.

A foreign-slot answer is UNKNOWN, never absence and never confirmation. This is not
a softer refusal: an answer about someone else's slot carries no information about
ours, so reporting absence asserts something nobody observed, and reporting
confirmation IS the wrong-slot commit the check exists to stop. Unknown with a
located cause cannot publish success.

Evidence, claim_batch on the discovered path: 8 witnesses PASS, source restored
IDENTICAL after mutation. The two foreign-slot witnesses guard DIFFERENT
properties, which the mutations establish rather than assume: removing the slot
check breaks only an_answer_about_a_foreign_slot_cannot_publish_success, while
reporting the mismatch as absence breaks that one AND
a_foreign_slot_answer_is_not_reported_as_absence. Neither witness alone covers the
wall, and the second is deliberately not discriminating for the first mutation --
counting two reds without separating them would have supported the false reading
that both guard the check's existence.

NOT ATTEMPTED, and the reason is in the CAS source rather than in effort: the
digest-to-payload pairing cannot be closed here. content_hash_of_value is not
generic over T, so no total hashing function over an arbitrary payload exists to
derive the digest from the value, and a check at this layer would be validation
that cannot verify anything. That obligation belongs to the byte-owning store,
which does not exist until a real origin does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…his invocation

A DEFECT IN THE LANDED FOLD, found on review. CasPreconditionFailed mapped straight
to ReadbackAbsent, short-circuiting before the independent read, and that is wrong
in precisely the case this module exists to serve. A precondition failure proves
only that the requested conditional transition did not happen on THIS invocation;
it does not prove the desired durable state is missing.

The slot key is derived from the attempt, so the occupant of attempt A's slot is
most often A's OWN earlier write. That makes the failing path ordinary rather than
exotic: A writes, the acknowledgement is lost, the retry asks create-if-absent, the
store answers precondition-failed because A's manifest is already sitting there,
and the old branch reported ABSENT for a manifest that is committed and readable --
failing a job whose result was safely stored, which is the exact inversion of the
property M0 exists to establish.

THE REPAIR REMOVES A BRANCH RATHER THAN ADDING ONE. Once the principle is stated --
the read decides what durable state EXISTS, the write outcome decides only what
happened to this invocation -- all three StoreAnswered arms collapse into one. The
CasOutcome was never legitimately deciding durable state; it was doing a job that
belongs to the observation. The fold is now driven by the read and consults the
transport for exactly one thing: whether "not found" is ESTABLISHED (the store
answered) or merely UNOBSERVED (it did not).

A conflicting occupant is still caught, one layer up rather than by this branch:
the readback digest is compared against the recorded manifest digest, so a foreign
manifest at our slot reports a content mismatch instead of success. The
reconciliation is earned by the digests matching, never by the arm.

Evidence, claim_batch on the discovered path: 11 commit witnesses PASS, 23
completion witnesses unaffected, source restored IDENTICAL.

THE REGRESSION CONTROL IS PERMANENT, NOT RETIRED. Reinstating the old
precondition-failed-to-absent branch turns
a_precondition_failure_reconciles_when_our_manifest_is_read_back red and NOTHING
ELSE -- the conflicting-manifest and no-object witnesses do not move, because they
guard the opposite over-correction. That separation is measured rather than
assumed; counting three new witnesses and one red mutation would have wrongly
credited all three with catching this. The short-circuit is the tempting shape, since
"precondition failed means somebody else holds it" reads as conservative, so the
probe that was red before the repair stays enrolled as the wall against its return.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
THE HOLE THIS CLOSES WOULD HAVE HOLLOWED OUT THE MILESTONE. OriginReadback and
ManifestReadbackObservation carried no source, so a confirmation derived from a
local cache read was indistinguishable from one derived from the durable origin --
and the authority would publish success. "Durably committed" was satisfiable by
reading the copy on the machine that is about to be lost.

It is not a far-fetched confusion, it is the expected one. The cache and the origin
address the SAME immutable object by the SAME content key, so the bytes and the
digest are identical and only the source separates them. Same content identity,
different AUTHORITY. The sentence that makes it concrete: the local copy is the
same CAS, so committing to it counts as durable. It must not.

ReadSource is now carried on the confirming read, and a DisposableCacheRead yields
Unknown rather than confirmation -- unknown rather than absence, because a cache hit
says nothing about what the origin holds in either direction. The two witnesses are
a matched pair by construction: identical digest, identical transport, opposite
verdict, so the refusal is attributable to the SOURCE and not to anything about the
content. Mutating the cache arm to confirm breaks exactly the cache witness and
leaves its origin twin green.

RUNG, STATED HONESTLY RATHER THAN IMPLIED. This is a DECLARED BOUNDARY, not a
construction, and it sits at mitigatable. DurableOriginRead is freely authorable
from any module, so a caller can still assert origin provenance for a cache read;
nothing here makes that unwritable. What changed is that the distinction is
STATABLE and REFUSABLE where before there was nowhere to record it at all -- the
class moves from unmodelled to declared, which is a smaller claim than a wall and
the honest one.

NEXT-RUNG TRIGGER, and it is the same one std.materialization_provider already
records against its own readback assertion: constructor confinement, so that a
ReadSource of DurableOriginRead can be minted only by the modeled origin read
rather than hand-built. That needs a byte-owning origin to exist, so it is
genuinely blocked on the origin rather than unbuilt -- and it is deliberately NOT
claimed as done here.

Evidence, claim_batch on the discovered path: 13 commit witnesses PASS, 23
completion witnesses unaffected, source restored IDENTICAL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
PARENT CORRECTION ACTED ON. The M0 invariant is a SEPARATION, not a placement:
durable truth and warmth are different products, every local byte is rehydratable,
the origin's failure domain is DISJOINT from the worker's, and success is readback
from the origin whichever origin that is. Off-fleet is one realization satisfying
that, not the requirement -- bringing durable bytes back on-fleet must stay a second
binding rather than a redesign.

AUDIT RESULT: the two modules were already clean of the thing that would have
foreclosed it. No provider, vendor or placement vocabulary in either; imports are
std.types, std.content_hash and std.durable_compare_and_set, which is itself modeled
as a shape rather than a transport. The completion authority never learns where
bytes live, and ReadSource separates DurableOriginRead from DisposableCacheRead on
AUTHORITY rather than location -- so an on-fleet origin binds as another realization
of the same interface.

WHAT THE AUDIT DID FIND, same class, smaller: commit_slot_key spelled
"fabric-m0/commit/" inline. A versioned namespace is a binding decision owned by
whoever selects and configures the origin, so a literal prefix inside the fold is
business policy sitting in an interface -- the layer inversion whose tell is exactly
this, a literal carried where a parameter belongs. The namespace is now received as
CommitNamespace.

The KEY stays derived, because that half has a correctness argument the namespace
does not: a caller-supplied key beside a caller-supplied attempt is two spellings of
one fact that can disagree, which is the shape std.durable_compare_and_set had to
correct once already. Receiving the namespace and deriving the key from it keeps one
authority for the derivation while leaving the namespace free to be versioned or
re-homed.

WALL LIVENESS RE-ESTABLISHED AFTER THE REFACTOR, not assumed. The change edits the
exact expression the slot check tests, and the witness spells the expected slot
LITERALLY while the module derives it -- two independent spellings, which is what
keeps the comparison from collapsing into value-equals-itself. Both mutations still
break exactly their own witness: removing the slot check breaks the foreign-slot
witness, treating a cache read as confirmation breaks the cache witness. 13 PASS,
source restored IDENTICAL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…presentable

The completion authority carried `readback` as a FIELD of each committed row and
of the commit manifest. A readback therefore existed for every committed object
by construction, and the question "was this object actually read back?" had no
representation: the nearest expressible thing was ReadbackUnknown, a value a
caller had to deliberately choose. Both halves of the digest comparison arrived
from one caller, so a wholly satisfied assessment established that somebody
wrote down a confirmation, not that a read happened. That is the seeded-true
fold answering "complete" over a population it never examined.

Split the read out as its own population and let the AUTHORITY perform the join.
OriginObservation { subject, readback } is supplied separately; each required
subject looks up its own observation; a subject with no observation is
Undetermined -- not Unsatisfied, because an unperformed read says nothing about
whether the object is there -- and two observations for one subject refuse
rather than letting position pick the agreeable one. The unexamined case is now
the DEFAULT state of an empty population rather than an option a caller must
remember to select.

The commit boundary DERIVES the manifest's observation from the transport and
the read rather than accepting one, so the only observation under that subject
is bound to the slot the manifest actually lives at.

What this does not establish, stated plainly: nothing here proves a reader ran.
That is the observation-to-slot boundary std.durable_compare_and_set already
declares outside its guarantee. Manifest membership is likewise still not bound
to the bytes whose digest was read; closing that needs a SHA-256-over-bytes
primitive, which std does not have (sha256_hex_digest PARSES a hex string, it
does not compute a digest from Bytes). That missing primitive is the next-rung
trigger, not a stall.

Evidence: 26/26 completion and 13/13 commit witnesses pass. Seeding the no-read
case to ObligationSatisfied reddens exactly the two new pending witnesses and
nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…ts trigger as a capability

DESIGN 4b obliges a discovered class to file a row. This one is already
rostered: gunbc.recurring_failure_mode.subject_and_its_digest_as_independent_parameters
describes a value and a summary of it standing side by side with no arm
relating them, which is manifest_digest beside members exactly, and it already
declares both the repair and a capability trigger. Minting a new class would be
net concepts growing by re-invention, so this appends a receipt instead.

WHAT THE RECEIPT ADDS. Applying that row's repair in .dag separates two shapes a
name search conflates: extdeps.crypto.hash sha256_digest takes HEX, so an author
asserts a digest and nothing computes it, while extdeps.tools.sha256sum
sha256sum_file_digest_via_shell takes a PATH and COMPUTES over the bytes there
with a typed unavailable arm -- a section 3 handler bound to a digest shape, one
of N, live in gunbc.instruments.fabric_control_plane_live_probe. No .dag
function takes Bytes and returns a digest, so the repair is reachable exactly
where the subject bytes exist at a path.

ANSWERED EXPLICITLY RATHER THAN BY DEFAULT: this instance is NOT blocked. Its
subjects are stored objects, so a durable-origin readback that stages what it
fetched puts those bytes at a path and the chain is buildable at a mitigatable
rung, with the in-substrate SHA-256-over-Bytes primitive as the CLIMB. The
constraint that realization must respect is recorded with it: the digested file
must be the origin readback's own output and never a cache copy, or a correct
computation over the wrong provenance launders the cache-read refusal in
fabric_m0_commit manifest_readback_for_slot into a confirmation.

RECORDED, NOT REPAIRED: this row's trigger ends by naming gunbc.guarantee_stall
wet_route_model_lags_seed_stall as the population it retires, and no such row
exists; content_digest_makes_annotations_semantically_load_bearing cites the
same absent name. The nearest survivor, self_host_wet_route_receipt_lifetime_stall,
is about route families and receipt lifetime and is not this population, so
repointing it would invent the join this class files. Under 4b(3) a trigger
naming a population that does not resolve is retired by nothing.

An earlier draft of the receipt claimed every digest in the corpus is asserted
or verified out-of-band. That folded the two shapes together and is withdrawn in
the text that lands.

Evidence: 26/26 completion witnesses pass; docs/design-failure-modes.md
regenerated from the authority via generated_artifact_gate main_wet_one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
gunbc-ci-auto-heal added 3 commits September 7, 2026 02:18
# Conflicts:
#	docs/design-failure-modes.md
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md subject_and_its_digest_as_independent_parameters
Ledger-Repair-Judged: docs/design-rung-drops.md
gunbai-bot Bot pushed a commit that referenced this pull request Sep 7, 2026
resolve_output_obligations now takes requirements and refuses a foreign
work_key (WorkKeyDoesNotMatchContract). Declared frontier: warm-moth-142
admit_job_publication (#10641). Reclassify content-addressed digests as opaque.

Co-authored-by: Cursor <cursoragent@cursor.com>
This module minted AttemptId = NonEmptyStr where brand("AttemptId") for the
executor of a job. std.scoped_authorization already declares AttemptIdentity for
that entity -- the consumer identity a grant is issued to, where a grant for
attempt A refuses attempt B -- and gunbc.auth.approval_broker uses that same type
for who executes a work claim, in WorkClaimedBy, WorkCompletedBy and
WorkAbandoned. One concept under two names is the section 3 nicknaming
violation, and I introduced it by coining before DFS-ing the concept DAG.

Dissolved rather than carried. A second spelling outlives the mistake, and the
fabric-m0 gate is about to be wired to a carrier that binds work and attempt, so
a nickname here would propagate into that join rather than stopping at this
module. The concept's home is confirmed by existing consumers:
test.claim.approval_capability_witness and test.claim.approval_broker_witness
already import AttemptIdentity from std.scoped_authorization.

Independently reached by snappy-lark-51, who owns the OutputContractRef cut and
said it would flag the same fork.

No cycle: std.scoped_authorization imports only std.types, std.content_hash,
std.effect_grant and std.durable_compare_and_set.

Evidence: 26/26 completion and 13/13 commit witnesses pass. A rename that
typechecks can still have collapsed a comparison, so the attempt binding was
re-proved live rather than assumed -- dropping the attempt conjunct from the
committed-row filter reddens exactly
a_foreign_attempts_artifact_does_not_satisfy_this_one, and the module restores
byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
gunbai-bot Bot pushed a commit that referenced this pull request Sep 7, 2026
resolve_output_obligations now takes requirements and refuses a foreign
work_key (WorkKeyDoesNotMatchContract). Declared frontier: warm-moth-142
admit_job_publication (#10641). Reclassify content-addressed digests as opaque.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbc-ci-auto-heal added 2 commits September 7, 2026 09:40
# Conflicts:
#	docs/design-failure-modes.md
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md subject_and_its_digest_as_independent_parameters
Ledger-Repair-Judged: docs/design-rung-drops.md
briansrls pushed a commit that referenced this pull request Sep 7, 2026
…esolution (#10713)

* Split OutputContractRef: opaque identity stays; obligations become a sealed roster

OutputContractRef held both plain names and content-serialised obligation
payloads, so no consumer could tell which it held. Keep the opaque name,
put DeclaredOutputObligations on WorkContract, and mint terminal-success
rosters only through resolve_output_obligations (sole_constructor RED).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Stamp judgment policy on ResolvedOutputObligations; path-scope the sole_constructor rung

Warm-moth-142: revision identity alone does not answer start-vs-gate; the
sealed result now carries OutputObligationsJudgment. Also correct the
unwritable claim to path-scoped rung 4 on source→.dag with a named
degradation when a Rust seed consumer appears.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move OutputContractRef annotations to module-item grain

Body and field-inline // comments refuse under the annotation channel;
CI floor failed on WorkContract and required_build_contract_from_materials.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make Unreadable reachable and declare the gate frontier

resolve_output_obligations now takes requirements and refuses a foreign
work_key (WorkKeyDoesNotMatchContract). Declared frontier: warm-moth-142
admit_job_publication (#10641). Reclassify content-addressed digests as opaque.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Unit-variant Unreadable cause needs {} construction form

A bare `= WorkKeyDoesNotMatchContract` parsed as a type alias, so resolve
and heal both refused unresolved type / undefined variable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Match WorkKeyDoesNotMatchContract with unit-variant {} form

Co-authored-by: Cursor <cursoragent@cursor.com>

* Brand OutputObligationsRevision so foreign digests are ill-typed

Bare ContentHash alias let any digest inhabit the sealed revision field;
brand matches every other identity carrier in work.dag (§3 / §6).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Mint OutputObligationsRevision as branded NonEmptyStr

ContentHash is a sum; casting a variant into ContentHash-where-brand refused
at runtime. Same identity shape as WorkKey: serialize then brand.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop unused serialize_content_hash import from resolution witness

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record that obligation revision mints only Fnv1a64 structural

serialize_content_hash is family-asymmetric; this carrier never enters the
other arms, so revision identity is not riding the incidental width split.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep artifact vs terminal as kinds on one obligation roster

Concatenating required-build manifests made split and collapsed id lists
the same WorkKey. Kind is identity material; resolution still returns ids.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Name obligation kinds at construction instead of defaulting to artifact.

An ids-only constructor silently stamped OutputArtifactObligation, so terminal
subjects (floor summary, outputs-verdict) minted the wrong work key. Training
now owes a stable adapter id rather than reusing the contract digest.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Admit #10706 OutsideModeledGuarantee citations as next-rung triggers.

Declarations was failing the floor on three stamp required_capability refs
and one fixture absence. Those names must stay unresolved; PLANTED_CONTROL
is the wrong roster because resolve there means a lost control. Same join as

Co-authored-by: Cursor <cursoragent@cursor.com>

* Seal resolved obligations with kind, not ids alone.

The work key already treated artifact vs terminal as distinct owed subjects;
dropping kind at resolve made same-id mixed rosters collapse for the gate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Enumerate the next-rung trigger helpers on the declaration-index seed-growth roster.

The four production fns and the discriminating test landed in the same boundary the
justification already lists at item grain; leaving them off the roster was a silent delta.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Enroll NEXT_RUNG_TRIGGER_CITATIONS on the declaration-index seed-growth roster.

The join helpers were listed; the production roster they close over was not.
PRE_EXISTING_CITATION_DEBT is already a DeclarationRef on the same justification.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep the sealed-roster kind note and drop the #10718 occupancy comment.

PLANTED_CONTROL stays empty; #10706 stamp sites remain on NEXT_RUNG_TRIGGER_CITATIONS.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
gunbc-ci-auto-heal and others added 7 commits September 7, 2026 16:40
product.fabric.work (gunbc#10713) landed the carrier this gate was waiting on,
and it retires the §3c consumption frontier that PR names: admit_job_publication
now takes an OutputObligationsResolution instead of a caller-supplied coproduct.

THE DEFECT THIS CLOSES. The caller used to CHOOSE between a declared roster and
an unreadable one, so a caller that never read the obligations could declare
empty and mint terminal success. ResolvedOutputObligations is sole_constructor
and its only mint is resolve_output_obligations, so an empty roster is now a
RESOLVED value that cannot be asserted here. The witnesses had to change shape
to match: they construct a real WorkContract and resolve, because a hand-built
resolution is unwritable.

WHAT THE SEAL DOES NOT CLOSE, AND THIS GATE NOW DOES. A sealed resolution is
unforgeable but still SUBSTITUTABLE -- a caller may hold a perfectly valid,
internally consistent resolution for work B while asking the gate to judge work
A. Nothing upstream can catch that, because only the gate knows which work it
was asked about. admit_job_publication therefore takes the WorkKey it is judging
and refuses a mismatch.

THREE TYPES AND A DUPLICATED VERDICT PATH DELETED, all my own coinage.
RequiredArtifactId and RequiredReceiptId forced two committed-row types and two
verdict functions differing only in type name, so the duplication looked like
domain structure while being invented vocabulary. One OutputObligation replaces
them. Kind is part of the key, not decoration: an artifact and a terminal result
may share an id, so every join compares id AND kind.

PublishObligationsUnreadable is RE-HOMED, NOT DELETED. Making output_obligations
a required field of the contract makes "the roster could not be read" unwritable,
which would have left that arm uninhabitable -- the decoration 4b calls worse
than absent. It survives because two reachable states now produce it: a typed
OutputObligationsUnreadable, and a resolution bound to a foreign work.

Evidence: 29/29 completion and 13/13 commit witnesses. Both new walls are
mutation-controlled and each RED is specific -- removing the work-key comparison
reddens only a_resolution_for_another_work_cannot_publish_success; dropping kind
from the join reddens only the_same_id_under_another_kind_does_not_discharge_it;
the module restores byte-identical. Two fixtures initially failed because they
judged a resolution derived from a different contract, which is the new wall
working rather than a defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
…ion/warm-moth-142

# Conflicts:
#	docs/design-failure-modes.md
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md subject_and_its_digest_as_independent_parameters
Ledger-Repair-Judged: docs/design-rung-drops.md
DESIGN 3c admits a declared frontier only with its trigger stated beside
it. publish_after_commit was annotated as "the consumer" while itself
having no production consumer: outside the enrolled witnesses, nothing
imports either fabric_m0 module. That honest state was inferable only
from an import census, not from the module.

State it in the module, and state the trigger as the CAPABILITY that
consumes it -- a job-publication path returning declared outputs by
content through a readable manifest -- rather than as one caller or one
job, which would be satisfied while the capability stayed dead.

No declarations change; the gate's walls stay enrolled and executing, so
this is a consumption frontier and not a 4b(3) rung drop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
# Conflicts:
#	dag/gunbc/recurring_failure_mode/subject_and_its_digest_as_independent_parameters.dag
#	docs/design-failure-modes.md
DESIGN 4b(1) takes a class's rung as the MINIMUM across its in-scope
paths, and rung inflation is worse than sitting low because an inflated
class never ranks for climbing.

Two annotations claimed rung 4 absolutely: the family-pinned digest
carrier as "structurally impossible", and the re-homed unreadable-roster
state as "unwritable". Both hold on the source-to-dag acceptance path
only -- the emitted Rust mirror of these carriers is a public struct with
public fields (gunbc.model.population), so a consumer writing Rust
against the seed is outside the guarantee.

Both now state that scope with the named degradation condition (no such
Rust consumer today) instead of an absolute. product.fabric.work already
states this for its own seal; the same caveat was owed here.

Annotations only -- no declaration changes. 29 completion witnesses
discovered and passing, including both discriminating walls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjN9HVXtvsmHak11cPDHSd
@briansrls
briansrls marked this pull request as ready for review September 8, 2026 19:30
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-08T19:39:11.086640Z 688a4a0 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 688a4a0023

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

resolution: OutputObligationsResolution,
judged_work: WorkKey,
committed: List<CommittedOutput>,
observations: List<OriginObservation>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Carry provenance for every output readback

When a required artifact or receipt exists only in a disposable cache, the caller can still encode that cache lookup as an OriginObservation containing ReadbackConfirmed, because this parameter has no ReadSource equivalent to the manifest path. required_output_verdict then treats a matching digest as satisfied, so a durable manifest plus cache-only outputs can produce PublishSuccess, violating the durable-completion guarantee. Derive or source-tag required-output observations at the same origin boundary as the manifest observation.

Useful? React with 👍 / 👎.

Comment on lines +159 to +162
ReadbackUnknown { cause: "manifest read from disposable cache " + pr + ", not the durable origin; durability unestablished" }
}
ManifestReadUnavailable { cause: rc } => ReadbackUnknown { cause: rc }
ManifestNotFound =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve provenance on negative manifest reads

When ManifestNotFound comes from a disposable cache rather than the durable origin, this branch maps it to ReadbackAbsent for every StoreAnswered outcome because, unlike ManifestRead, the not-found arm carries no source. The gate can therefore finalize failure and trigger re-execution even while the manifest remains present at the origin; only an origin-sourced not-found result should establish absence.

Useful? React with 👍 / 👎.

Comment on lines +235 to +238
observations: concat(
observations,
[manifest_observation(namespace: namespace, attempt: attempt, transport: transport, observed: observed)]
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude caller-authored manifest observations

When the supplied observation collection already contains a CommitManifestSubject—which its public List<OriginObservation> type permits—this unconditional append creates two observations for that subject. readback_verdict_for rejects any duplicate, so even two identical durable confirmations become PublishFinalizationFailed; narrow this input to obligation observations or remove/refuse caller-supplied manifest subjects before adding the derived one.

Useful? React with 👍 / 👎.

CasCommitted/CasPreconditionFailed/CasStoreRefused were imported alongside
CasOutcome but never matched on; ObservationSubject likewise. The only
remaining textual hit is an annotation sentence, not a reference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1wPj9dCqfaPT4G2AZyKnS
@briansrls
briansrls merged commit 82e29d5 into main Sep 8, 2026
4 checks passed
@briansrls
briansrls deleted the session/warm-moth-142 branch September 8, 2026 20:39
@briansrls
briansrls restored the session/warm-moth-142 branch September 8, 2026 20:44
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