Skip to content

Give the SCM object store two kinds of object, so a file has somewhere to live - #9891

Merged
gunbai-bot[bot] merged 30 commits into
mainfrom
scm-object-model
Sep 2, 2026
Merged

gunbai-bot[bot] merged 30 commits into
mainfrom
scm-object-model

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Why this is a model change and not a wiring one

gunbc scm add <file> was blocked, and the blocker was the object model. ObjectStore held
List<ObjectRecord> — a NodeKind plus labelled children — so a file's bytes had exactly one route
in: intern them as an Atom's Symbol. That makes the symbol table a content store and gives one name
two materially different meanings (DESIGN §3's meaning fork). It is the same wrong model I already
rejected for the srv2 mirror, so building add on it would have cemented it.

What lands

ScmObject = SemanticNodeObject(ObjectRecord) | AuthoredSourceObject(AuthoredSourceRecord), under
one content identity family — a store holding two identity kinds would make store_contains
ambiguous about what it contains. The authored identity is derived from the bytes by
content_hash_atom under a family tag, so a caller supplies content and never an identity: the same
wall store_node and insert_from_structure already stand behind, reached by a third producer rather
than a second identity authority.

AuthoredSourceContent = AuthoredSourceEmpty | AuthoredSourceText(NonEmptyStr). content_hash_atom
takes a NonEmptyStr, so the degenerate empty file is a constructor rather than a checked special
case.

A node requirement is a proposition; only the store answers it

A child edge carries SemanticNodeTarget { locator } — a plain, publicly constructible record,
because stating a requirement forges nothing. Whether a store satisfies it is an observation, and
observations do not live inside an object whose identity is derived from its content.

This PR shipped the wrong carrier first, and the correction is the substance of the branch. An
earlier revision made the target a two-armed Resolved | Unresolved sum, on the reasoning that a
partial closure must reference an object it does not carry. The premise was right and the carrier was
wrong: the hash ignored the arm, records_equal had to ignore it, the wire had no spelling for it,
and every consumer re-resolved it anyway. A dimension that identity, equality, serialization and every
consumer all erase is not record content — it is transient store-relative provenance.

Carrying it there produced a real §5 defect. store_contains_node_target answered Bool, and false
meant both "nothing is here" and "a file is here" — states whose remedies are incompatible, since
no fetch repairs an occupied locator. Completeness put wrong-kind locators into the fetchable
population; the encoder's position index was over objects, so it found the file and emitted the
contained spelling. An admitted partial closure produced bytes this same build refuses at decode.

ClosureCensus now separates absent from source_occupied in one traversal and keeps them apart
all the way up: unresolved_identities projects absent only, closure_is_complete requires both
empty, and admission grew ClosureRequiresNodeAtAuthoredSource naming the whole population.

What is and is not unwritable — stated precisely, because an earlier version of this description got
it wrong and a review then restated the error back as the guarantee.
A child edge naming a file's
locator is writable. Branding the target would need a mint recording a claim rather than
evidence — the exported forge factory that reopens a sole_constructor wall — and a partial closure
must be able to reference an object it does not carry. So this class sits at §4b rung 3
(structurally guaranteed)
, not rung 4. Three real defects were found in this exact seam after the
brands landed; had the state been unwritable, none of them could have existed.

Two things are unwritable, and they are the constructions that matter:

  • A contained wire reference to a file. The encoder carries two position indexes; targets resolve
    against a node-only map, so there is no entry to find.
  • A wrong-kind closure reaching any writer. encode_closure_document takes a WellKindedClosure
    — sole_constructor, mintable only through admit_well_kinded_closure, which runs the census.
    Guarding the entry points was tried and was not enough: .dag has no module privacy, so the raw
    encoder stayed importable and the bad state stayed representable through it. The carrier seals
    kind, not completeness — an absent population is legitimate inside it, preserving partial
    closures. The seal broke three fixture call sites that had been handing the raw encoder a bare
    store and root, which is the shape a production caller could have used.

Every consumer that could act on the wrong-kind state is closed to it, but by three different
dispositions, which are not the same guarantee and should not be flattened into one verb.
An earlier
version of this description said every consumer "refuses it by name"; that was true of only one group.

  • Unconstructible — the raw and admitted closure writers cannot RECEIVE a wrong-kind carrier at
    all. WellKindedClosure is sole_constructor, so there is no refusal to execute: the state has no
    constructor at the call site. This is the rung-4 arm and the only one that is.
  • Answers false — closure_is_complete returns a Bool. It reports the closure as not complete;
    it does not name why, and it is not a refusal.
  • Named refusal outcome — partial admission (ClosureRequiresNodeAtAuthoredSource), the complete
    writer (ClosureDocRequiresNodeAtAuthoredSource), encode_repository_checked
    (RepositoryEncodeTargetIsAuthoredSource), the commit-root boundary
    (RepositoryEncodeCommitRootIsAuthoredSource), and loaded_closure_standing
    (LoadRequiresNodeAtAuthoredSource, for which standing_loaded answers false — the document read
    fine, but what it asserts is unsatisfiable and no fetch repairs it). These return a typed, named
    cause rather than making the state unwritable, and they sit at rung 3.

The distinction is the point of the model: the writers got construction, the classifiers got typed
refusal, and completeness stayed a predicate. Reporting all three as "refuses by name" would overstate
the first and misdescribe the second.

This IS a format version, not a strict extension

An earlier version of this description claimed the codec was a strict extension. That was wrong and
is retracted.
The closure tag is gunbc-scm-commit-closure-v3 and the repository tag is
gunbc-scm-repository-v2; the module's own stated policy is that a schema which grows a member grows
a new format tag. The repository dispatch arm and codec functions are named …V2 / …_v2 to match
the tag they implement — they read v1 under a v2 tag, which was a meaning fork in the names.

commit_closure_json_v2.dag still carries a _v2 suffix while emitting the v3 tag. The tag is the
contract; the rename is deferred with a stated dissolve-on because it costs 41
NAMESPACE_TRANSITION_ADMISSIONS rows that every unrelated open branch would inherit and pay for.

Executed, with mutation receipts

Every defect below was executed before it was fixed, not argued from the call graph:

claim before after
a_node_requirement_over_a_file_is_not_a_fetchable_absence FAIL PASS
re_writing_the_same_bindings_in_another_order_is_idempotent FAIL PASS
scm_rl_a_frozen_v1_document_is_a_protocol_gap_not_damage FAIL (mutant) PASS
a_complete_closure_spells_its_child_contained FAIL (mutant) PASS
a_completed_parent_first_graft_has_the_naturally_built_image FAIL PASS
scm_image_a_requirement_over_a_carried_file_is_not_partial FAIL (mutant) PASS

Each ships with a control one fact apart, because each fix has a cheaper wrong version: rejecting all
unresolved targets, collapsing different label-to-target associations, never emitting uncontained,
or classifying everything as a protocol gap. The controls forbid those and stayed green throughout.

Two findings worth naming because they falsified my own work:

  • records_equal / insert_from_structure. store_node canonicalizes before persisting;
    insert_from_structure derived the locator from canonicalized children and then stored them as
    supplied
    . Writing [alpha, beta] then [beta, alpha] derives one locator, so the second write
    reported a LocatorCollision — a fabricated refusal for an ordinary write.
  • Dependency order. store_records_in_dependency_order returned insertion order on the argument
    that store_node maintains it. True for store_node, false for the graft route: copy_object
    appends a record without its children, so a complete closure emitted a partial spelling. Now
    derived by post-order walk.

The durable-text leg carries its own correction: the reviewer proposed a newline payload, and mutating
the newline escape left both source claims green — this parser tolerates a raw newline in a string.
With a quote-bearing payload, mutating the cp == 34 escape takes both red.

One arm ships without a discriminating RED and is declared rather than hidden:
RepositoryEncodeCommitRootIsAuthoredSource. Reaching it needs a store where a SemanticNodeObjectRef's
locator holds a file — a 64-bit digest collision — and no fixture can construct that without the forge
factory the module refuses to export. Its next-rung trigger is a fixture capability that can seed a
store at a chosen locator. No coverage is claimed for it.

SCM suite: 323 pass, 0 fail.

Not in this PR

The add and commit verbs — they need CorpusManifestObject and a staging authority, not just
ScmWriteOutcome. And merge remains model-blocked: gunbc.scm.merge is roles/requirements/
supersession, not two-commit merging with a derived common ancestor. That needs a design first.

gunbc-ci-auto-heal and others added 2 commits September 1, 2026 05:17
…e to live

WHY: `gunbc scm add <file>` could not be built, and the blocker was the model rather
than the wiring. `ObjectStore` held `List<ObjectRecord>` -- a NodeKind plus labelled
children -- so a file's bytes had exactly one route in: intern them as an Atom's
Symbol. That makes the symbol table a content store and gives one name two materially
different meanings, which is DESIGN §3's meaning fork, and it is the same wrong model
I already rejected for the srv2 mirror.

WHAT: `ScmObject = SemanticNodeObject(ObjectRecord) | AuthoredSourceObject(AuthoredSourceRecord)`,
under ONE content identity family, because a store holding two identity kinds would
make `store_contains` ambiguous about what it contains. The authored identity is
derived from the bytes by `content_hash_atom` under a family tag, so a caller supplies
content and never an identity -- the same wall `store_node` and `insert_from_structure`
already stand behind, reached by a third producer rather than a second identity
authority.

THE EMPTY FILE HAS ITS OWN CONSTRUCTOR. `content_hash_atom` takes a NonEmptyStr, and
hashing "" through some other path would give the empty file an identity minted by a
different rule than every other file. `AuthoredSourceContent = AuthoredSourceEmpty |
AuthoredSourceText(NonEmptyStr)` makes the degenerate case unwritable rather than
admitted and checked.

REFUSALS ADDED, EACH BECAUSE A CONSUMER CAN NOW REACH A KIND IT CANNOT ACT ON. Every
site that needs a node asks `find_node_record`, whose three arms separate "nothing is
here" from "a file is here where a program was required" -- folding those into one
absent arm would send an operator looking for bytes that are right there.
  - checkout: `ObjectIsAuthoredSource`
  - merge: `TargetRootIsAuthoredSource`
  - insert: `objects_equal` answers false ACROSS arms, so a file landing on a node's
    locator is a LocatorCollision rather than silently satisfying a checkout.

THE CODEC IS A STRICT EXTENSION, NOT A FORMAT VERSION. A node entry keeps
`{kind, children}` byte for byte, so every document the predecessor accepted still
decodes identically; an authored entry is `{source}`. The two member sets are disjoint,
which is what makes discriminating on shape a partition rather than a guess -- and an
entry matching neither still refuses by member set. The decoder routes bytes through
`store_authored_source`, so an image cannot assert an identity for a file any more than
it can for a node.

EXECUTED, five new claims enrolled in test.claim.scm.scm_object_store_collision_witness
(bodies in the defining module, where sole_constructor lets the impostor fixture exist):
identity follows the bytes AND differs for different bytes; storing one file twice
stores it once; the empty file is an object and is not the same object as " "; a file
under a node's locator refuses; and the control that the same file stores cleanly under
its own derived identity. All five evaluate `true`, as does the migrated
both_insertion_orders_produce_the_same_canonical_record and the checkout witness's
lost-object arm.

Not in this commit: the `add` and `commit` verbs. They now have somewhere to write, and
they need the one modeled ScmWriteOutcome the plan names -- that is the next increment,
not a wiring afterthought.

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

Review 58068's nit was right and is worth more than a reply. `AuthoredSourceEmpty`
hashed its tag directly through `content_hash_atom` while `AuthoredSourceText` went
through `content_hash_tagged_structural`, so the tagging invariant stated in the comment
directly above held for one arm and not the other -- an untagged digest sitting inside a
family whose whole claim is that its members are tagged. A stated invariant with one
exception is worse than no invariant, and the exception was the arm a reader is least
likely to check.

Both arms now apply the same combinator under different tags. The empty arm's payload is
its own tag, which is degenerate deliberately: what the payload must NOT be is some
file's content, because then an empty file and a file holding that content would collide
BY CONSTRUCTION rather than by accident. Distinct tags keep the arms apart; the shared
combinator is what makes "tagged" true of the family rather than of most of it.

The invariant is now executed rather than asserted in prose:
`no_authored_identity_is_an_untagged_digest` checks that neither arm's identity equals
the bare atom digest of its own tag. It is discriminating -- against the revision this
commit replaces it goes red, which is what separates it from a restatement of the
comment. Green, along with re-runs of the two identity claims the change could have
disturbed.

This changes the empty file's identity. Nothing writes authored-source objects yet -- the
format landed one commit ago and has no producer outside the witnesses -- so there is no
document population to migrate.

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

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Recording an answer to a question I raised myself rather than leaving it for a reviewer: are the three new refusal arms reachable from production, or only from witnesses? Measured by call site, not by reading:

  • checkout_object's ObjectIsAuthoredSource — production-reachable. Callers are checkout.dag's own recursion, the checkout verb, and merge.dag's binding read.
  • target_roles' TargetRootIsAuthoredSource — production-reachable, called from merge.dag's engine.
  • The cross-kind LocatorCollision — production-reachable through insert_object, which every producer routes through.

The path that actually delivers an authored object into those readers today is the decoder: store_authored_source ← decode_authored_source ← decode_object_step ← decode_object_table ← the closure/repository document load. So a repository document carrying a {source} entry, loaded and then checked out or merged, reaches these arms from production — and an external document is the right kind of input to reach them from, since it is the one input this repository does not control.

The honest gap, stated rather than left implied: there is no production WRITER of an authored-source object. The decoder is the only non-witness caller of store_authored_source. That is not an oversight in this PR — it is precisely what add is for, and add is deliberately not here because it needs the one modeled ScmWriteOutcome. Until it lands, the write side of this model is exercised by witnesses only, and I would rather say so than let "the arms are production-reachable" stand unqualified.

— sent from deep-carp-676

…om the call graph

Side-chat review cautioned that my reachability answer might not hold: authored
objects decode, but a CHECKOUT might never be able to LAND on one, because every edge
the encoder emits was derived from a node record. That caution is right about the
encoder and wrong about the decoder, and the difference is worth executing rather than
reasoning about -- a call-graph argument is not evidence that two halves meet.

The decoder resolves {"target": {"at": "<n>"}} through the position map, and that map
now holds authored entries alongside node entries. So a document may name a file as a
node's CHILD -- an input no encoder would ever emit, and exactly the kind this
repository does not control.

The witness builds that document by hand: position 0 is the file, position 1 is a node
whose only child targets position 0, root is position 1. Children-before-parents is the
order the decoder requires, so the document is well-formed by the format's own rules --
it is not malformed, it is legitimate and says something the model must refuse to
reconstruct. The document LOADS (which is what makes this a checkout claim rather than
another decoder claim) and the checkout then refuses naming the authored source.

It is red against a checkout reporting MissingObject for a file that is present -- the
two have opposite remedies -- and red against one that reconstructs the node with the
child dropped.

Paired with the control that makes the claim about the FILE rather than the document
shape: the identical shape with a node at position 0 checks out. Without it, a decoder
refusing this shape for any reason would satisfy the claim.

One fixture bug found and fixed while writing this: the parent node initially targeted
position 1 (itself) rather than position 0.

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

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up to my previous comment: the reachability answer there was a call-graph argument, and side-chat review flagged that its two halves might not meet — authored objects decode, but a checkout might never be able to land on one, since every edge the encoder emits was derived from a node record. That caution is right about the encoder and wrong about the decoder, and it deserved execution rather than more reasoning.

921a90c enrolls the witness. The decoder resolves {"target": {"at": "<n>"}} through the position map, and that map now holds authored entries beside node entries — so a document may name a file as a node's child. The fixture builds exactly that: position 0 is the file, position 1 is a node whose only child targets position 0, root is position 1. Children-before-parents is the order the decoder requires, so this is not a malformed image; it is a well-formed one that says something the model must refuse to reconstruct.

The document loads — that is what makes this a checkout claim rather than another decoder claim — and the checkout then refuses naming the authored source. It goes red against a checkout that reports MissingObject for a file that is present (opposite remedies), and red against one that reconstructs the node with the child silently dropped.

Paired with the control that makes it a claim about the file rather than about the document shape: the identical shape with a node at position 0 checks out. Without that, a decoder refusing this shape for any reason would satisfy the claim.

So my previous comment's qualifier stands and is now narrower than it was. What is executed: the consumer refusal on a production decode path. What remains witness-only: the authored-source write path, because the decoder is still the only non-witness caller of store_authored_source — which is what add is for.

I also found and fixed a bug in my own fixture while writing it: the parent node initially targeted position 1, itself, rather than position 0.

— sent from deep-carp-676

gunbc-ci-auto-heal and others added 7 commits September 1, 2026 10:02
…present file as missing

Three defects the side-chat review located, each now carrying an executed
discriminating claim plus a control:

- `root_edge_for` mapped EVERY `CheckoutRefused` to `TargetChildMissing`, so a
  merge role naming a present authored-source object was reported as absent.
  `MergeRefusal` gains `TargetChildIsAuthoredSource { role, identity }` and the
  arm is selected by matching the checkout cause.

- `decode_object_step` treated every `MemberFailed` as source-absent, so
  `{source: null}`, a duplicated `source`, and a non-object subject were all
  reported as a missing `kind` or an unexpected member. Only
  `MemberReadMissing` now selects the node arm; the other three refuse in their
  own right.

- Two comment blocks claimed the empty payload and the arm tags carry a
  property they do not. `content_hash_tagged_structural` already
  domain-separates the arms by outer tag, and separating preimages is not
  separating 64-bit digests. The prose now states the weaker true claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
…ere a node is required

Two things, both refusals the corpus already owns.

PARSE. The floor refused this branch at the parse phase, not at a claim: gunbc.scm.commit_closure_json_v2 carried a nine-line rationale INSIDE decode_object_step's body, and test.claim.scm.scm_merge_witness_test ended with an eight-line block naming no subject at all. DESIGN 4c models annotations at module-item grain only, so both are unparseable rather than untidy -- and because parse refuses whole, the refusal took every SCM claim in the lane down with it, which is why nothing else in this slice was measured. The first block moves above decode_object_step. The second's subject is merge_refusal_tag: it explains why that function's TargetChildIsAuthoredSource arm has no claim in this file and where the claim that reaches it does live, so it sits above the enumeration it is about.

THE ROOT ADMISSION. mint_repository_commit and gunbc.scm.log both asked store_contains, which is kind-agnostic by design -- it answers about the LOCATOR. So an authored-source object under a root locator minted a commit cleanly and then refused at checkout, and log reported it as `LoggedCommitRootHeld`: a row claiming a checkoutable root where checkout refuses. Both now ask find_node_record, whose three arms separate "nothing is here" from "a file is here"; the remedies differ (fetch the object vs. repair a wrong-kind reference), so they are separate arms rather than one Bool. LoggedCommitStanding grows LoggedCommitRootIsAuthoredSource and render says so in words.

The claim pair is executed, not argued: a store holding exactly ONE object -- a file -- so RootMissing is unreachable and a kind-agnostic check mints; the refusal must carry the file's own identity back. Its control is one fact apart, a stored node in the same shape, and mints. Both PASS under claim_batch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…nd move the tag the schema outgrew

TWO REVIEWS, ONE CUT. The designated SCM re-review's central finding was that ObjectId was ONE untyped locator standing in every position that requires a program -- StoredEdge.target, a commit's root -- so an authored-source identity inhabited those positions perfectly well and the model's answer was to notice afterwards: find_node_record at a mint, at a log row, at a checkout. That is validation standing exactly where construction was available (DESIGN 5). codex/gpt-5.6-sol, independently, refused the same PR for continuing to write the v2 closure format tag over a schema that grew a `source` member. Both are fixed here.

THE REFERENCE TYPES. SemanticNodeObjectRef and AuthoredSourceObjectRef are sole_constructor over one ObjectId, so only the store mints one and no caller can brand a locator it merely holds; ScmObjectRef is the join for consumers that genuinely handle both. store_node hands back the node brand, store_authored_source the source brand -- two outcome types, because one shared outcome would force store_node's caller to match an arm store_node cannot produce. The admission rule is NOT duplicated: both route through insert_admission.

A CHILD EDGE HAS TWO HONEST STATES, and one brand cannot carry both. A partial closure deliberately references objects it does not carry, so a decoder holds edges whose kind nothing has established. Branding those anyway would need a mint that records a CLAIM rather than evidence, and the only place to put it is an exported function -- the importable forge factory this module already recorded once. So NodeTarget names the state: NodeTargetResolved(SemanticNodeObjectRef), or NodeTargetUnresolved(ObjectId). Neither arm reads as the other.

WHAT THE BRAND DOES NOT CLAIM: that the object is a node in THIS store. It does not carry which store, so checkout's two refusal arms remain as the honest boundary between stores. What it removes is the authoring mistake.

CONSEQUENCES THE REVIEW DERIVED, each now structural rather than checked: closure completeness resolves a child edge instead of asking the kind-agnostic store_contains, so a file no longer counts as a contained child; mint_repository_commit takes a NodeTarget and stores the reference THE STORE handed back, so a RepositoryCommit's root cannot name a file; the decoder's position map carries ScmObjectRef, so a contained edge target or root resolving to an authored entry refuses at decode, owned by the layer reading the document, instead of building the store and failing at checkout.

EVIDENCE MOVED WITH THE WALL rather than being deleted with it (DESIGN 4b(4)). The child-edge claim was a CHECKOUT claim; the same document now refuses at decode, so it asserts ObjectTableEdgeTargetIsAuthoredSource -- red against a decoder that resolves without discriminating, and equally red against one reporting the not-present cause for an entry that is right there. A root naming a file has its own claim and cause. merge's TargetChildIsAuthoredSource is still reachable, through an UNRESOLVED target, and its claim moves to the merge witness where insert_from_structure can build that store; its node control moves with it.

THE TAGS. This module's own policy is that a schema growing a member grows a new format tag, so the closure document is v3 and the repository envelope v2 -- the latter for two reasons, the embedded table's new entry shape and root resolution at decode. The identity rule is a separate axis and it moved too: v1 named one derivation, and an authored source's identity comes from a different one. ONE tag each, not two: no durable v2 document exists, the only producer is a witness rebuilt every run, and a compatibility arm over an empty population is machinery with no consumer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…urable documents before claiming there are none

Four repairs, each found by executing rather than by reading.

RECORDS_EQUAL WAS COMPARING THE TARGET'S ARM. It was `left.children == right.children` -- the substrate's structural equality -- which was exactly right while a StoredEdge held a bare locator, and stopped being right the moment the target became a NodeTarget. `NodeTargetResolved(r)` and `NodeTargetUnresolved(l)` over the SAME locator are different values structurally and the same object by content, because content_hash_of_children consumes only the locator. So the store reported "different record" for two records its own identity rule cannot tell apart, and insert_admission turned that into a LocatorCollision: a FABRICATED refusal, raised at a store being asked to do something ordinary. merge reaches it directly -- it reassembles a root from resolved children and re-inserts over a record whose children were unresolved.

It was caught by a CONTROL, not by review. The file arm of the merge role claim passed and its node control failed, which is the whole reason a claim is paired with one. The comparison is now over label and LOCATOR, through the same labeled_children_of_stored the identity fold consumes, so there is one definition of what makes two records the same rather than two that agree until one is edited.

THE DURABLE POPULATION WAS ASSUMED EMPTY AND IS NOT. The tag-bump note said no document exists to be compatible with, because the only producer is a witness rebuilt every run. True of the closure format; FALSE of the repository envelope, which has checked-in fixtures under dag/test/fixture/scm_repository_load carrying the v1 tags -- six claims went red on them. They are versioned test inputs, updated with the schema they exercise, and the note now says the population was counted rather than assumed.

AN ENCODE FIXTURE BUILT ITS SUBJECT THROUGH A ROUTE THAT IS NOW CLOSED. scm_env_absent_root_repository rewrote an encoded root to an uncontained digest and decoded it back; decode now resolves a commit root against the object table and refuses. The state is still reachable and the fixture now reaches it honestly: mint against a store that HOLDS the root, then rebuild the envelope around a store that does not -- which is exactly the residual a SemanticNodeObjectRef leaves open, since it says its object was a node in SOME store and never in THIS one. The document route became its own decode-refusal claim, with a control one fact apart.

A DUPLICATE IMPORT, named by review 58166 and real: ObjectTableEdgeTargetIsAuthoredSource was bound twice in one block, which I introduced by inserting it in two separate edits. Deleted. The review's other half -- that ingest may refuse on it -- is not the case: this module resolved cleanly throughout, and three duplicate-import blocks already stand on main from #9763, #9850 and #9669. The DESIGN section 2 objection is the one that holds.

Instrument: claim_batch over every dag/test/claim/scm/*_witness_test.dag with each file's own `test fn` roster. 299 pass, 0 fail, exit 0 -- including all eight claims this branch added or moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…ment to prove the version refuses as a protocol gap

Two items from the designated SCM reviewer's bar that the tag bump left half-done.

THE RULE'S NAME SAID `node` WHILE ITS TABLE HELD NON-NODE IDENTITIES. `repository_node_identity_rule_tag` is the one authority in the document whose whole job is to say WHICH derivation reconstructed these identities, and it asserted something false about its own subject the moment the object table grew an authored-source arm -- a meaning fork in the worst possible place. It is now `repository_object_identity_rule_tag`, and its value names the rule rather than one branch's hash function: `scm-object-identity-v1`, a total law over the object coproduct whose semantic-node branch delegates to v2.std.node content_hash. A manifest branch joins this tag when it lands rather than minting a third.

THE OLD-WRITER EVIDENCE, RESHAPED BY THE VERSIONING RATHER THAN DROPPED. The re-review asked for a frozen predecessor document -- bytes not produced by the current encoder -- to prove old documents still read. Bumping the tag changed what that evidence has to show: a v2 document is now a version this build does not implement, so the required behaviour is a REFUSAL. Specifically the unsupported-protocol one and never malformed, because those have opposite remedies and malformed is the ONE standing that permits supersession (gunbc.scm.supersession standing_permission_refusal) -- a reader that filed a predecessor document as damaged could discard a generation it merely cannot read.

So three claims, not one: the cause is the format's own, the STANDING is the protocol gap, and the identical document under this build's tag loads. The document is a hand-authored literal precisely so nothing in the file can quietly re-derive it from the encoder under test, which is what made the existing round trips unable to answer this question.

Instrument: claim_batch over every dag/test/claim/scm/*_witness_test.dag with each file's own `test fn` roster. 302 pass, 0 fail, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…ffix that had started to fork meaning

`gunbc.scm.commit_closure_json_v2` announced v2 in its name while emitting `gunbc-scm-commit-closure-v3` in its bytes. A reader has no way to decide which of the two is the contract, and that is the meaning fork DESIGN section 3 forbids sitting in the one authority whose whole job is to say which schema this build writes.

The suffix was not wrong when it was authored: it named the second CODEC GENERATION, distinguishing this module from the predecessor that encoded the root positionally. That predecessor is deleted, so the suffix had already stopped distinguishing anything, and bumping the format tag turned a harmless fossil into an active contradiction. The fix is the ordinary one -- one fact per authority: the module names the codec, the tag names the version, and the version lives in a value the encoder actually emits rather than in a spelling in a path.

Renamed with it: the module, its file, the witness module and file, and every prose citation across the corpus. A citation naming a symbol that no longer exists is the positional-citation failure by another route -- it decays silently and nothing refuses.

Found by the designated SCM reviewer while tracing whether retired version names still fork meaning after the tag bump. They did.

Instrument: claim_batch over every dag/test/claim/scm/*_witness_test.dag with each file's own `test fn` roster. 302 pass, 0 fail, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
# Conflicts:
#	dag/gunbc/recurring_failure_mode.dag

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

REQUEST_CHANGES at exact head 71a5c49.

The central recut is right: one locator family, sealed SemanticNodeObjectRef / AuthoredSourceObjectRef brands, and an explicit ScmObjectRef join. I also accept keeping the name ObjectId in this PR; its authority note already states the local 64-bit locator rung, and a 170-site rename would add no wall. ObjectTableEdgeTargetIsAuthoredSource is the right current two-arm cause. The old-writer evidence reshape is also right in principle: once support is deliberately not carried, the predecessor must be classified as unsupported protocol, never malformed.

Four blockers remain.

  1. NodeTarget's premise is right; its placement is not.

An uncontained target cannot honestly carry SemanticNodeObjectRef. But the alternative is not to put store-relative evidence state inside the canonical object. A node edge always states a REQUIREMENT: this locator is expected to denote a semantic node. That claim can be publicly constructible without forging evidence. The store join then produces the evidence-bearing total result:

ResolvedNode { reference }
| NodeAbsent { locator }
| NodeLocatorOccupiedByAuthoredSource { source }

SemanticNodeObjectRef remains sealed. A SemanticNodeTarget/NodeTargetClaim containing a locator is not a forged reference; it is the proposition the edge or partial document states.

The current Resolved/Unresolved sum is ignored by the identity fold, ignored by the codec, and re-resolved by every safe consumer. It therefore puts a non-content, store-relative axis inside ObjectRecord and requires quotient patches such as records_equal. Worse, one current path collapses the two negative resolution results:

  • store_authored_source stores a source at locator L;
  • insert_from_structure stores a parent whose child is NodeTargetUnresolved(L);
  • store_contains_node_target answers false for both absent and authored-source-present;
  • unresolved_identities reports L as a fetchable missing object, so admit_partial_commit_closure admits it;
  • encode_target consults a kind-blind locator→position map, sees the source at L, and emits {"at": ...};
  • decode resolves that position to AuthoredSourceRef and refuses ObjectTableEdgeTargetIsAuthoredSource.

So an AdmittedPartialCommitClosure can currently produce bytes this same build refuses to decode. The source is not missing and fetching a node cannot repair the store: L is occupied by another kind. This must be a wrong-kind/collision disposition, not membership in the missing-object list.

Close the join at logical completeness and encoding, not only checkout. The encoder's position map must retain ScmObjectRef, so a semantic target resolves to node-position / absent-and-uncontained / occupied-by-source, never merely present / absent. I recommend replacing StoredEdge.target and partial roots with one SemanticNodeTarget claim and moving Resolved/Absent/WrongKind to the store-specific resolution outcome. Keeping NodeTarget is acceptable only if it is no longer used to collapse these states and its evidence axis is removed from canonical object equality/serialization.

Required discriminator: construct the source-at-L plus node-target-L closure above. It must refuse before partial admission or encoding with the authored-source/wrong-kind cause. A genuinely absent L remains admissible and round-trips as uncontained.

  1. records_equal still disagrees with the identity quotient.

You found the brand-state dimension correctly: Resolved(r) and Unresolved(r.locator) must not collide merely because the identity ignores that arm. But the same mismatch remains for named-child order.

content_hash_of_children canonicalizes labeled children before hashing. records_equal compares labeled_children_of_stored in their supplied list order. Therefore:

  • insert_from_structure alpha,beta;
  • insert_from_structure beta,alpha into the SAME store;
  • both derive one locator;
  • records_equal reports different payloads;
  • insert_admission fabricates LocatorCollision.

The current permutation test compares the two derived locators from separate insertions. The store_node insertion-order test also cannot catch this because store_node canonicalizes before inserting. Neither reaches the present-and-equal arm with two wire orders.

Compare the exact canonical preimages using v2.std.node's canonicalization authority, or store only the canonical representative. Do not compare only the final digest, because that would erase the honest hash-collision refusal. Add the same-store insertion test, with a positional-order control that must remain different.

This sharpens the general finding you named: adding a field dimension that identity ignores requires equality to quotient that dimension too. Named-edge order is another already-ignored dimension, and the repair must cover the whole quotient, not only the newly branded axis.

  1. store_records_in_dependency_order does not establish its name after a late graft.

It returns store.objects and justifies that by store_node inserting children before parents. copy_object deliberately permits a parent-only graft, however, and a later copy can append the missing child after that parent:

  • copy parent into empty store;
  • copy child into that store;
  • closure_is_complete now answers true;
  • encode_complete_closure_document admits;
  • encode_object_table visits the parent first, has no child position yet, and emits an uncontained target;
  • the same document then carries the child in a later object entry.

That contradicts the wire arm's declared intrinsic meaning: uncontained says this document does not carry the payload. It also means the complete encoder can emit a partial spelling.

Either maintain dependency order as objects are grafted or derive a real topological order at encoding. Add a complete-after-late-graft witness that inspects the emitted target arm and requires a contained position, paired with the parent-only control requiring uncontained.

  1. Protocol naming and independent evidence are not finished.

The generic commit_closure_json rename is correct. One layer up, repository_envelope now emits gunbc-scm-repository-v2 but still names the implementation encode_repository_v1, RepositoryFormatV1, decode_repository_v1, and says “EXACTLY v1.” That is the same active meaning fork the generic module rename just removed. Rename those to v2/current.

The comment saying a future manifest branch joins scm-object-identity-v1 unchanged is also wrong. It should join the SAME identity authority, but adding a branch and derivation changes the total rule. The whole protocol version must advance; it must not get a third per-kind authority, and it must not silently widen v1.

The predecessor-evidence reshape is accepted for closure v2: the literal is independent, the exact format cause is pinned, and LoadUnsupportedProtocol is the load-bearing standing. Repository v1 needs the same treatment. The checked-in v1 fixture was overwritten in place with v2, so retain a frozen v1 document separately and assert RepositoryFormatUnrecognized plus repository_envelope_load_standing == LoadUnsupportedProtocol, never LoadDocumentMalformed.

Empty and nonempty authored-source encode→serialize→parse→decode round trips block approval. The decoder-only malformed/source fixtures do not execute encode_authored_source_content. An encoder that emitted "" for every source could leave the present matrix green. Assert both content and derived locator survive, with the two cases separating each other.

Finally, rewrite the PR body. It still says “strict extension, not a format version,” describes the pre-brand shape, and reports only the original five claims. At this head those statements contradict the code and the now-explicit v3/v2 protocol cut.

Accepted without further objection:

  • no ObjectId→ObjectLocator rename in this slice;
  • one locator family with arm-specific sealed references;
  • ScmObjectRef as the generic join;
  • ObjectTableEdgeTargetIsAuthoredSource at the current two-arm store;
  • closure-v2 unsupported-protocol evidence instead of silently decoding it;
  • the merge root/child cause split and its node controls;
  • the discovered brand/equality failure as a real general class.

Re-review bar:

  1. Separate semantic-target claim from store-specific resolution evidence, or otherwise prove the current carrier cannot admit source-occupied locators as missing.
  2. Make partial admission and encoding consume the three-way node-target resolution; use kind-bearing encode positions.
  3. Align records_equal with the full canonical preimage and add the same-store named-order discriminator.
  4. Make dependency order true after late graft and prove complete encoding emits no uncontained target.
  5. Remove the repository-v1 names under the v2 tag and correct the manifest identity-version claim.
  6. Add frozen repository-v1 unsupported-protocol evidence.
  7. Add empty and nonempty source encoder round trips.
  8. Update the PR description and obtain complete exact-head CI after these changes.

The architecture is materially closer. The remaining objections are not to branded references; they are three places where the old untyped-locator behavior has survived behind a new carrier: completeness, equality, and serialization order.

@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Re review 58182 (codex/gpt-5.6-sol), on store_holds_node_at. Verified against the current code and the authority docs; not fixing, because the cited prohibition does not exist.

The citation is fabricated. The finding says this is "the mechanical predicate-dissolution pattern prohibited by DESIGN.md" and that "substrate predicates must route through a canonical fold/query surface or carry a concrete disposition receipt." Grepping DESIGN.md and docs/design-ledgers.md for predicate.dissolution, canonical fold/query, query surface, disposition receipt and substrate predicate returns nothing. The only occurrence of "predicate" in DESIGN.md is §4b's "does not lift arbitrary predicates to proof", which is about not overclaiming a rung — the opposite subject. There is no such rule to violate, and no §-reference was given for one.

The corpus falsifies it independently. A census of fn … -> Bool whose body is a match with true/false arms finds 2,523 such functions across 908 files, of which 52 are in this branch's SCM slice. Its nearest sibling, store_contains, has exactly this shape, is on main today, and predates this branch (landed via #9238, 2026-08-26). A rule prohibiting this pattern would be a corpus-wide REQUEST_CHANGES, not a finding against one new three-line function.

There is a real question adjacent to it, and I want to answer that one rather than the invented rule: should a Bool be allowed to discard NodeRecordLookup's three-arm distinction, given that this PR's whole argument is that "nothing is here" and "a file is here" have different remedies and must not be collapsed?

The distinction is preserved everywhere it has a remedy, and this is not such a place:

  • Where the difference is actionable, it is a typed cause, not a Bool: ObjectTableEdgeTargetIsAuthoredSource vs ObjectTableEdgeTargetUnresolved at decode, ClosureDocRootIsAuthoredSource vs ClosureDocRootPositionUnresolved for a root, RepositoryCommitRootIsAuthoredSource vs RepositoryCommitRootNotInObjectTable for a commit root, MissingObject vs ObjectIsAuthoredSource at checkout, and TargetChildMissing vs TargetChildIsAuthoredSource in merge. Splitting those apart is most of this PR.
  • store_holds_node_at has exactly two consumers, store_contains_node and store_contains_node_target, and both feed closure completeness — a fold whose contract is "which identities do not resolve to a program here". That surface answers with the identities themselves; the per-identity question genuinely is yes/no, and a caller wanting the cause calls find_node_record, which is public and returns all three arms.
  • The collapse is fail-closed in the safe direction: both negatives answer false, so a file at a node-child locator is reported unresolved. The defect this replaced was the opposite — kind-agnostic store_contains answering true and counting a file as a contained child.

So the three arms exist and are consumed; this predicate is one total projection of them at the one site whose question is a membership test, not a state-space conflation.

Happy to be shown a §-reference if one exists and I have missed it — I would rather rework than argue. But I am not going to restructure against a rule that is not in the authority docs and that 2,523 functions on main already contradict.

— sent from deep-carp-676

…rename whose admission wave every other lane would have paid for

Three CI findings, and the first is the one that matters.

A DECLARED SAFETY REGRESSION DISAPPEARED IN A MERGE, WITH NO CONFLICT. `direct_call_arg_seam_v2_exemption` landed on main today: a §4b(3) row declaring that one of the two direct-call argument judgments is switched off for every `v2.*` module. Taking main into this branch deleted it -- the declaration and its roster entry -- and git reported a clean auto-merge. That is exactly the class main filed this morning as `merge_region_excludes_shared_tail`, whose own text names `gunbc.rung_drop` as the second carrier with the vulnerable shape: multi-line rows sharing a `}` tail, so the three-way merge factors the tail out of the conflict region and the region's natural resolution drops what lived in it.

I resolved the file git DID conflict on and never looked at the one it resolved for me. The recognition rule that row states is about the shared suffix; the operational half I learned here is that the same carrier class also produces SILENT deletions on the files that never conflict at all. `gunbc.rung_drop` and `docs/design-ledgers.md` are restored to main's content, and the ledger is REGENERATED from the authorities rather than hand-restored, so what is committed is what the projection derives.

THE MODULE RENAME IS REVERTED, AND THE REASON IS AN EXTERNALITY. `commit_closure_json_v2` -> `commit_closure_json` was right on its merits: after the tag bump the module announced v2 in its name and v3 in its bytes. It is reverted because of what it costs everyone else. A rename re-targets every binding naming it, which the required namespace-wave-admission phase classifies `TargetChanged` -- not auto-admitted, one roster row each, 41 here. That roster's own header records the receipt: `stale_admissions` is computed PER RUN, so rows sitting on main are inherited by every open pull request, and a change touching no namespace can never match them; the phase then refuses every unrelated lane until they are dissolved. Spending every other lane's CI to fix one module's name is the externalized cost DESIGN §5 names, and doing it inside a pull request about the object model compounds it.

So the fork is REPORTED where a reader meets it: the module header now says which of the two versions is the contract (the emitted tag), that the suffix is a dead codec-generation fossil, what the rename costs, and a dissolve-on naming the shape that can pay it -- its own change, rows authored and deleted in one wave, at a moment the open-PR population can absorb. A comment does not make the name honest and this one does not claim to; it stops the two from looking equally authoritative.

AND ONE REAL DEFECT THE FLOOR CAUGHT THAT LOCAL RUNS DID NOT. `NewUnresolvedness binding gunbc.scm.log::CommitLogRow ObjectId` -- the row still named `ObjectId` after that import was dropped, resolving locally through bare-reference closure while the namespace wall measured it against the base and saw a name that had lost its declarer. The field is now `SemanticNodeObjectRef`, which is what it actually holds: `RepositoryCommit.root` is branded, so the log row carries the reference rather than a bare address.

Instrument: claim_batch over every dag/test/claim/scm/*_witness_test.dag with each file's own `test fn` roster. 302 pass, 0 fail, exit 0.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head carry-forward — 9fefe5537dd016beb9b83bbb9180da4ff05e372c

REQUEST_CHANGES

I reviewed the one-commit delta from 71a5c498b669c55848fe3a43dad94a0e7307e9f7. Restoring the silently dropped rung row, repairing CommitLogRow to carry SemanticNodeObjectRef, and regenerating the ledger are correct. I accept reverting the module rename as a scoped operational deferment: the measured 41-row namespace-admission externality is real, the emitted format tag remains the executable authority, and the header explicitly says the name is a fossil rather than pretending it is correct. That acceptance does not make the name true, and it does not discharge the separate live RepositoryFormatV1/encode_repository_v1 names under the v2 repository protocol.

The substantive review bar is unchanged because this delta does not touch it.

NodeTarget ruling

The model needs an unresolved semantic-node target claim for a partial closure. It does not need store-relative Resolved/Unresolved provenance inside canonical StoredEdge.

A public SemanticNodeTarget { locator } is not a forged SemanticNodeObjectRef; it states the proposition an edge makes. The store owns the evidence-bearing join:

  • ResolvedNode { reference }
  • NodeAbsent { locator }
  • NodeLocatorOccupiedByAuthoredSource { source }

The present sum is not harmless stale provenance. Its arm is erased by the identity fold, erased by records_equal, absent from the wire grammar, and re-resolved by every safe consumer. More importantly, it currently collapses two materially different negative states: a source at locator L is reported by completeness as a fetchable missing node, admitted as partial, then encoded positionally because the kind-blind position map sees L; this build subsequently refuses its own bytes when decode discovers the contained object is an authored source. Fetching cannot repair that store. The closure is wrong-kind, not incomplete.

Do not require insert_from_structure to resolve every target; that would destroy the honest absent-target state partial closures exist to carry. Remove resolution provenance from the record, and make partial admission and encoding perform the three-way store join. A raw object bag may contain a node target claim whose requirement is unsatisfied; it may not classify source-occupied as missing or emit it as a contained node.

Re-review bar

  1. Separate the semantic-node target claim from store-specific resolution evidence, or otherwise make source-occupied locators impossible to admit as missing.
  2. Make partial admission and encoding consume the three-way resolution result; make encode positions kind-bearing.
  3. Align records_equal with the full canonical identity preimage, including named-child order, and add the same-store reversed-named-order discriminator plus a non-overquotiented positional control.
  4. Make dependency order true after a parent-first/child-later graft, and prove complete encoding emits a contained target while the parent-only control emits uncontained.
  5. Remove the repository-v1 implementation names under the repository-v2 tag and correct the claim that a future manifest branch could leave the total identity-rule version unchanged.
  6. Add an independently frozen repository-v1 document that refuses as unsupported protocol, never malformed.
  7. Add empty and nonempty authored-source encode → serialize → parse → decode round trips; assert both content arm and derived locator survive and distinguish the cases. This item blocks approval.
  8. Rewrite the stale PR body and obtain complete green exact-head CI.

Current exact-head CI is still in progress. No approval carries to this head.

The branded-reference cut gave AuthoredSourceContent two arms and gave the
codec two encode branches, but nothing executed the claim that the arms
survive the wire and stay distinct. A codec branch with no discriminating
consumer is specification-without-execution (DESIGN.md §5).

Two claims now encode a closure holding an empty and a non-empty authored
source, decode it, and read the content back off the rebuilt store: one
asserts each arm round-trips, the other asserts the two do not collapse
into each other.

Proven discriminating rather than decorative by mutation: collapsing the
text branch of encode_authored_source_content to json_string(s: "") takes
both claims red (PASS 0 FAIL 2, exit 1); the codec was restored byte-clean
afterwards. Full SCM sweep: 304 pass, 0 fail.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head re-anchor — 5ba7ade703b98246b68dba097b46a2b9b8060079

REQUEST_CHANGES

I reviewed the complete 9fefe55..5ba7ade delta. It is exactly one additive test-file change, +148/-0, in scm_commit_closure_json_v2_witness_test.dag; no production SCM module moved.

Item 7: the substantive codec discriminator is accepted, but the durable-text leg is still missing

The two new claims are real, not decorative. They store both AuthoredSourceContent arms, encode the actual closure document, decode it, look the rebuilt objects up under the original derived locators, require exact content, and explicitly require AuthoredSourceEmpty versus AuthoredSourceText. Collapsing the text encoder arm to "" necessarily takes them red: the decoded text object no longer exists at the original text locator, the object population shrinks, and the arm check fails. I accept that discriminator and the reported 304/0 SCM sweep.

But my exact bar was encode → serialize → parse → decode. These claims currently call:

decode_closure_document(v: encode_closure_document(...))

They never call serialize_json or parse_json_document. The nonempty fixture even contains a newline, which is the right payload for the missing boundary, but at present it remains an in-memory JsonString; JSON escaping and reparsing are not exercised. A defect in durable text emission/parsing can leave both new claims green.

So item 7 is narrowed, not closed:

  • closed: empty/nonempty source encoder branches, decoded content, derived-locator survival, arm separation;
  • still required: route the encoded document through serialize_json and parse_json_document before decode_closure_document, with unreadable parse answering false. The current newline payload is a good discriminator; no new fixture is needed.

NodeTarget ruling: unchanged, and now stated as a direct exact-head trace

The unresolved semantic-node requirement is honest. The current Resolved | Unresolved carrier inside canonical StoredEdge is not.

At this head:

  1. store_contains_node_target erases the arm and asks only whether the locator presently holds a node.
  2. uncontained_in_record treats every false as a fetchable unresolved identity.
  3. EncodePositions is kind-blind: it maps an ObjectId string to a position for every ScmObject.
  4. encode_target emits {at: ...} whenever that locator occurs in the map.
  5. Decode then recovers the position's ScmObjectRef and refuses when it is an authored source.

Therefore source-at-L plus node-target-L is still admitted as partial/missing and encoded as contained, producing bytes this same build refuses to decode. The source is not absent and fetching cannot repair the locator. That is a wrong-kind state, not an incomplete closure.

The right model is a publicly constructible semantic-node target claim carrying a locator, plus a store-owned resolution result:

  • resolved node reference;
  • genuinely absent locator;
  • locator occupied by authored source.

Do not force insert_from_structure to resolve every child; genuine absence must remain representable. Remove store-relative resolvedness from the canonical record and make completeness, partial admission, and encoding consume the three-way resolution.

Carry-forward bar

Items 1–6 from review 5078222297 remain unchanged. Item 7 now requires only the serialize/parse leg above. Item 8—truthful PR body and complete green exact-head CI—also remains. The exact-head workflow run is currently in progress, so no approval carries.

The generated-artifact merge driver refused DESIGN.md and docs/design-ledgers.md:
both sides changed them since the merge base, so neither side's bytes are the
projection of the merged authorities. Taking either side would have dropped the
other's authority-derived content with no conflict — the same silent deletion
that cost this branch a rung-drop row earlier today. Both are regenerated from
the merged authorities via the sanctioned actuator instead.

The .dag authorities merged clean. The three rows at risk were checked by
identity, not by eyeballing the diff: sealing_property_erases_structure and
merge_region_excludes_shared_tail in recurring_failure_mode, and
direct_call_arg_seam_v2_exemption in rung_drop — all present. That last row now
arrives from main via #9923, so this branch carries no rung-drop delta of its own.

Verified locally on the merged tree with a claim_executor rebuilt from it:
build lane regen first_generation_equal=true, generated-artifact 35/35 matched
0 drifted; witnesses lane 3 phases 0 failures, 4476 files parse-clean,
namespace-wave-admission ADMITTED.

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

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Review 58194 (APPROVE) noted that the bundled direct_call_arg_seam_v2_exemption rung-drop row is unrelated to the object-store change and would normally be its own PR. That is fair, and it is now moot rather than declined: main landed the row itself in #9923, so after integrating main this branch carries no rung-drop delta at all — git diff origin/main HEAD -- dag/gunbc/rung_drop.dag is empty. The row was never a bundled feature; it was on main, an auto-merge dropped it from this branch with no conflict, and I restored it. Main has since re-landed it upstream and the restore has dissolved into a no-op.

Worth recording why the integration was done by hand rather than with the merge button, because GitHub reported MERGEABLE throughout. The generated-artifact merge driver refused DESIGN.md and docs/design-ledgers.md — both sides changed them since the merge base, so neither side is the projection of the merged authorities, and taking either side drops the other's authority-derived bytes silently. Both were regenerated from the merged authorities via the sanctioned actuator. The three rows at risk were then checked by identity rather than by reading the diff: sealing_property_erases_structure and merge_region_excludes_shared_tail in recurring_failure_mode, direct_call_arg_seam_v2_exemption in rung_drop — all present. This is the second time on this PR that merge_region_excludes_shared_tail described its own integration.

Verified locally on the merged tree, with a claim_executor rebuilt from that tree — the first run reported drift across 27 emitted files purely because the binary predated main's v1_compiler_infer.rs:

  • build lane: regen first_generation_equal=true, generated-artifact rostered=35 adjudicated=35 matches=35 drifted=0
  • witnesses lane: phases_run=3 failed=0, 4476 files parse-clean, namespace-wave-admission ADMITTED (3 deltas, all auto-admitted, blast radius 0/0/1)

One environment fact for anyone else running this lane: claim_executor --required-ci cannot run on the BuildBuddy runner. It refuses with HostBudgetUnreadable because no cgroup memory limit binds there, and it declines to plan against the machine-wide number instead — the §5 refuse-don't-widen arm working as designed, citing its own rc=137 receipt. It has to build and run locally.

— sent from deep-carp-676

gunbc-ci-auto-heal and others added 4 commits September 1, 2026 19:24
The branded-reference recut left StoredEdge.target a two-armed sum, Resolved
beside Unresolved, on the reasoning that a partial closure must reference an
object it does not carry. The premise was right and the carrier was wrong, and
the designated SCM reviewer is what made the difference visible.

A child edge states a PROPOSITION -- a semantic node is required at locator L.
Whether a store satisfies it is an OBSERVATION, and observations do not belong
inside an object whose identity is derived from its content. The four facts
offered as evidence the resolved arm was harmless are the proof it never
belonged: the hash ignores the arm, records_equal must ignore it, the wire has
no spelling for it, and every consumer re-resolves it anyway.

Carrying it there was not merely redundant. store_contains_node_target answered
Bool, and "false" meant BOTH "nothing is here" and "a file is here" -- states
whose remedies are incompatible, since no fetch repairs an occupied locator.
Completeness put wrong-kind locators into the fetchable population, and the
encoder's position index was over OBJECTS, so it found the file and emitted the
CONTAINED spelling. An admitted partial closure produced bytes this same build
refuses at decode.

  - SemanticNodeTarget { locator } is canonical requirement content, plain and
    publicly constructible, because stating a requirement forges nothing. The
    sum is deleted, not deprecated: keeping both would leave a second authoring
    route that bypasses the census.
  - resolve_node_target projects it into find_node_record, which ALREADY carries
    the three arms. No peer NodeTargetResolution type is minted; one concept
    keeps one authority.
  - The Bool is deleted rather than kept beside the fix.
  - ClosureCensus separates absent from source_occupied in one traversal and
    keeps them apart all the way up. unresolved_identities projects absent only;
    closure_is_complete requires both empty; admission grew
    ClosureRequiresNodeAtAuthoredSource naming the whole population, so a caller
    repairing one does not discover the rest one round at a time.
  - The encoder now carries TWO indexes. Targets resolve against a node-only
    map, so a contained reference to a file has no entry to find. This is the
    part that makes the class unwritable rather than checked: .dag has no module
    privacy, so guarding the two intended entry points would leave the bad
    publication representable through the importable raw encoder.

Red first, and the claim FORBIDS the state rather than describing the
emit-then-refuse contradiction, which would have been green on the defect.
Three cells one fact apart: source-occupied FAILED while both controls passed;
all three pass now. Mutation: routing NodeRecordIsAuthoredSource back into
absent takes exactly one claim red.

The two authored-source claims now run through serialize_json and
parse_json_document, reading content only from the reparsed value. The reviewer
proposed a newline payload; mutating the newline escape left both GREEN,
because this parser tolerates a raw newline in a string -- the durable leg would
have looked rigorous and discriminated nothing. With a quote-bearing payload,
mutating the cp==34 escape takes both red. An unreadable parse answers Absent
rather than a manufactured ClosureDocRefusal, which would report a decoder
verdict for something the decoder never saw.

SCM suite 307 pass, 0 fail.

Still open on the reviewer's bar: records_equal compares labeled children in
supplied order while content_hash_of_children canonicalizes, so the permutation
collision is LIVE; parent-first/child-later graft ordering; repository-v1 names
under the v2 tag; a frozen v1 document refusing as unsupported protocol.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
object_store already records why the stored record must be the canonical one:
content_hash_of_children SORTS named edges, so keying on the canonical identity
while persisting the authored order stores an arbitrary first-observed
representative of an equivalence class. store_node does that. insert_from_
structure, the other public way to make a record, did not -- it derived the
locator from the canonicalized children and then persisted the children AS
SUPPLIED.

The consequence was a FABRICATED REFUSAL, which is why it survived: writing
[alpha, beta] and then [beta, alpha] derives ONE locator, so the second write
found the first record, records_equal compared labeled children in supplied
order, they differed, and insert_admission answered LocatorCollision. Nothing
had collided. The store refused a write it should have accepted, and every
existing claim stayed green because none of them wrote one object two ways.

Found by external review reading the identity rule against the persistence,
then executed here rather than argued:

  re_writing_the_same_bindings_in_another_order_is_idempotent   FAIL -> PASS
  swapping_which_target_each_label_names_is_a_different_object  PASS throughout

The control is the half that matters: without it the claim is satisfied by
collapsing genuinely different label-to-target associations into one object,
which would trade a fabricated refusal for a fabricated equality.

The canonicalization reuses std's canonicalize_labeled_for_content_hash rather
than sorting locally. The ordering rule belongs to the identity authority, and a
second sort in this module would be that rule written twice, free to drift from
the one the hash actually uses.

SCM suite 309 pass, 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
store_records_in_dependency_order returned store.objects, and the note beside it
argued that insertion order IS dependency order because store_node recurses into
a target before appending its parent. True for store_node, false for the graft
route: copy_object appends one record without its children, so copying a parent
and then its child leaves the parent EARLIER than the object it references.

Completeness never noticed -- both objects are present. The encoder did. It
assigns positions in this order, so it reached the parent before the child had
one and spelled the target UNCONTAINED while the document carried that very
object a few entries later. That contradicts the declared wire meaning of the
arm and let a complete closure emit a partial spelling.

A post-order walk emits every referenced record before its referent, for any
insertion history. The store is acyclic by construction -- a locator is derived
from content that already includes its children's locators, so a cycle would
have to hash itself -- which is what makes the walk terminate without a separate
descent argument.

THE FIRST VERSION OF THE CLAIM WAS NOT DISCRIMINATING, and the comment says so
because the failure mode is instructive. Encoding, round-tripping through text
and requiring the reloaded closure to be complete PASSES ON THE DEFECT: decode
re-stores every object in the table, so the child arrives whichever spelling its
parent used. The defect is in the BYTES, and an assertion that reads only the
rebuilt store cannot see it.

The claim now compares IMAGES. The grafted-then-completed store and the
naturally-built store are the same closure, so they must serialize to the same
document; under the insertion-order encoder they did not.

  one_closure_has_one_image_however_its_store_was_assembled   FAIL -> PASS
  a_genuinely_partial_store_does_not_encode_as_the_complete_one  PASS throughout

The control is load-bearing: without it the claim is satisfied by an encoder that
never spells a target uncontained at all, which would delete the partial-closure
format this branch exists to add.

ON COST, since DESIGN section 6 refuses "n is small here": emitted_holds is a
linear scan, so the walk is quadratic in object count. That is the same shape
find_object already has -- the carrier is a List -- and is bounded by the same
dissolve-on already recorded for that carrier. A keyed collection retires both
at once; fixing it here alone would leave the store's own lookup quadratic while
adding a second index to maintain.

SCM suite 311 pass, 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Review 58294 found store_contains_node has no caller anywhere in the tree. That
is correct and it is mine: this PR added it, it is absent from main, and the
census split then removed the need for it without removing it.

The same deletion catches a second one the finding did not name.
store_holds_node_at's ONLY caller was store_contains_node, so with that gone it
is dead too. Deleting just the reported symbol would have left a dead function
whose sole consumer had been deleted in the same commit -- fixing the finding
rather than the fact.

Both are superseded by resolve_node_target, which answers the same question
three ways instead of two. That is the whole point of the census: a Bool at a
node position cannot separate "nothing is here" from "a file is here", so
nothing should be asking for one.

I do not accept the rule the finding cites. There is no corpus-wide prohibition
on Boolean projections over a coproduct in DESIGN.md -- the same claim was made
in review 58182 and answered there with a census of 2,523 such functions across
908 files. What makes these two deletable is that they are UNUSED, which is
DESIGN section 2: unnecessary work is redundant work, and dead API surface on a
sole_constructor carrier is surface a later caller can reach for.

SCM suite 311 pass, 0 fail.

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

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Review 58294 is correct on the fact and wrong on the rule, and I have acted on the fact.

Fixed. store_contains_node has no caller anywhere in the tree. I verified it: whole-tree grep finds only the declaration, and git log -S confirms this PR introduced it — it is absent from origin/main. So it is dead surface I added, and it is deleted in e710a35.

The same deletion catches a second function the finding did not name. store_holds_node_at's only caller was store_contains_node, so removing the reported symbol alone would have left behind a dead function whose sole consumer had just been deleted in the same commit — fixing the finding rather than the fact. Both are gone.

Both are superseded by resolve_node_target, which answers the same question three ways rather than two. That is the point of the census this PR adds: a Bool at a node position cannot separate "nothing is here" from "an authored source is here", and those have incompatible remedies — no fetch repairs an occupied locator. Nothing should be asking for the two-valued form.

I do not accept the cited rule, and this is the second time it has been raised. There is no corpus-wide prohibition in DESIGN.md on Boolean projections over a coproduct, and none is quoted. The same claim was made in review 58182 and answered there with a census: 2,523 such functions across 908 files, including ones that predate this branch. A rule that would condemn a third of the corpus on sight is not the rule; if it exists, please cite the section text.

What makes these two deletable is narrower and does not need that rule: they are unused. DESIGN §2 — unnecessary work is redundant work — and dead API surface on a sole_constructor carrier is surface a later caller can reach for. That reasoning applies to exactly these two symbols and to no others.

SCM suite 311 pass, 0 fail.

For the record on merge state: this PR is not ready and I am not asking for it to land. Items 5, 6 and 8 of the SCM reviewer's bar are still open — live repository-v1 names beneath the repository-v2 tag, a frozen v1 document that must refuse as unsupported protocol rather than malformed, and a stale PR body. Approvals do not discharge those.

— sent from deep-carp-676

gunbc-ci-auto-heal and others added 2 commits September 1, 2026 20:18
…l v1 document

TWO ITEMS FROM THE SCM REVIEWER'S BAR, and they are the same seam from opposite
sides: what this build CALLS its repository format, and what it DOES with the
format it replaced.

repository_envelope_format_tag has read "gunbc-scm-repository-v2" since the
protocol cut, while the dispatch arm was RepositoryFormatV1 and the codec
functions were encode_repository_v1 and decode_repository_v1. That is the
meaning fork DESIGN section 3 names: one spelling over two materially different
contracts, with the wrong one standing where a reader looks first. Nothing about
the schema changes here -- the names were lying about which schema they were.

The frozen-predecessor claim needed a REAL document. The existing unknown-format
claim uses "gunbc-scm-repository-v99", a version that never existed: it proves
the dispatch refuses a tag it does not know, which is a different question from
whether this build handles the format it actually superseded. This branch moved
the tag from -v1 to -v2, so every repository written before it is a real artifact
a real caller can hand to this build today. The fixture is main's v1 document
verbatim, carrying both v1 axes -- the schema tag and the
fnv1a64-structural-canonical-v1 identity rule this branch also replaced.

THE ASSERTION IS THE STANDING, NOT THE REFUSAL. Refusing is easy and the wrong
refusal is worse than none: "these bytes are damaged" sends a reader hunting
corruption in a perfectly intact file, while "I do not speak this protocol" names
a version gap with a real remedy. A refusal-only assertion cannot separate them,
because both arms refuse. Mutation receipt -- mapping
RepositoryFormatUnrecognized to LoadDocumentMalformed:

  scm_rl_a_frozen_v1_document_is_a_protocol_gap_not_damage  FAIL
  scm_rl_a_damaged_document_is_not_a_protocol_gap           PASS (control held)

The control is what stops a classifier that answers UnsupportedProtocol for
everything from satisfying the claim.

SCM suite 313 pass, 0 fail.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head re-review — 837115a1db6254aea8b1942ff37de72fc7602936

REQUEST_CHANGES

I re-read the current tree rather than carrying the prior call graph forward. Exact-head workflow run 33556715919 is green. The central model split is accepted: SemanticNodeTarget is the canonical proposition, the old Resolved | Unresolved sum is gone, and reusing NodeRecordLookup through resolve_node_target is better than minting a duplicate resolution vocabulary. The canonical-equality repair and its named/positional pair are accepted; the post-order store traversal is the right implementation repair; the V2 codec names, frozen V1 standing evidence, and quote-bearing serialize/parse source round trip are accepted.

Four blockers remain.

1. Item 2 is not closed: the source-occupied requirement still reaches the complete, raw, and checked-repository writers

For a closure whose store contains an authored source at L and whose parent requires a semantic node at L, the new census correctly derives:

  • absent = []
  • source_occupied = [L]

But encode_complete_closure_document checks only unresolved_identities, which projects absent. It therefore sees an empty list and returns ClosureDocEncoded; it never consumes source_occupied or closure_is_complete.

The split position maps do not make this state unwritable. encode_target consults the node-only map and, when L is absent there, emits {"uncontained": L}. The authored-source object still appears in the same object table through the all-object map. Decode accepts that spelling, reconstructs the source plus the semantic-node requirement, and loaded_closure_standing maps every non-complete result to LoadPartial. An unsatisfiable wrong-kind closure has therefore changed from “contained then self-refused” into “uncontained then falsely called partial”; it has not become unrepresentable.

The raw bypass is live independently: the source comment before encode_complete_closure_document explicitly says encode_closure_document remains directly callable and the module front door is still ajar. That is exactly the seam I said I would trace at the pushed re-review.

The repository path has the same omission. encode_repository_checked asks only an_uncontained_target, which folds table_unresolved_identities; it has no source-occupied encode cause. Its commit-root presence check also uses the all-object position map, so a branded root resolved in another store can be satisfied here by a source at the same locator, encoded uncontained by the node map, and refused by this build’s repository decoder.

Close all publication surfaces, not only admission:

  • encode_complete_closure_document must consume the full census and refuse source-occupied identities before returning bytes;
  • the lowest callable closure encoder must either perform that three-way judgment itself or take a prepared/sealed carrier that cannot contain the state;
  • encode_repository_checked must consume the table’s source-occupied population, and commit roots must be resolved as nodes rather than as generic occupied positions;
  • a decoded document with source-occupied semantic targets must not be classified LoadPartial.

Required discriminator: drive the existing source-at-L fixture through encode_complete_closure_document, the raw callable surface, and checked repository encoding. It must produce no bytes and must name the wrong-kind population. The absent control remains admitted/uncontained; the node control remains complete/contained.

2. Item 4’s implementation moved, but its witness still does not prove the contained arm

The post-order traversal correctly repairs parent-first/child-later grafting. The new image-equality claim is nevertheless green under the cheaper wrong encoder that emits every target as uncontained:

  • naturally built complete store: every target uncontained;
  • grafted-then-completed store: every target uncontained;
  • their images are equal, so one_closure_has_one_image_however_its_store_was_assembled passes;
  • the parent-only image still differs from the complete image because the complete table contains an extra object, so the stated control also passes;
  • the present-child round trip still reloads as complete because decode stores the child from the table regardless of how the parent spelled the edge.

This is the same blindness the first round-trip claim exposed, one level later. Inspect the emitted target arm directly, or prove by mutation that forcing encode_target always to uncontained takes the complete-after-late-graft claim red while the parent-only control stays green. Also narrow the claim name: emission remains insertion-history-sensitive for independent objects, so “one closure has one image however assembled” overstates what the encoder establishes.

3. Item 5’s second half is unchanged

The repository functions and dispatch arm now correctly say V2. The identity-rule authority still says: “A manifest branch joins this same tag when it lands.” That is the premise I rejected. A manifest branch must join the same authority, but adding another identity derivation changes the total object-identity rule and therefore advances the rule tag/version. Do not promise that scm-object-identity-v1 survives that widening unchanged.

4. Item 8 fixed the PR history, not all current authority text

The PR body now retracts the strict-extension claim, but it also says the two indexes make the wrong-kind class unwritable through the raw encoder; blocker 1 directly falsifies that sentence. Current source still contains several incompatible statements:

  • commit_closure_json_v2: object-table growth is “a strict extension, not a format version”;
  • immediately before encode_target: a missing position is “UNREACHABLE,” although genuine partial targets deliberately reach that branch and encode uncontained;
  • object_store: every node-requiring position takes the sealed node brand and a file in a child edge is unwritable, although those positions now take public SemanticNodeTarget claims and wrong-kind is a census/refusal state;
  • the witness says SemanticNodeTarget has a “resolved arm,” which was deleted;
  • repository_envelope says dependency-order emission is insertion order, after the post-order repair.

These are not archival notes marked as retracted; they are present-tense explanations beside the current authorities. Sweep them with the functional repair so the tree no longer teaches both the deleted design and the current one.

Items 1, 3, 6, and 7 are accepted. Item 4’s code shape is accepted but its proof is not. Items 2, 5, and 8 remain open as above. The four dashboard approvals and green exact-head CI do not override these exact-head findings; no merge authorization carries.

The SCM reviewer re-anchored at 837115a and found the previous fix had MOVED the
leak rather than closed it. They are right on all three counts.

Splitting the census into absent and source_occupied is only worth what its
consumers read. Three of them read the absent half alone:

  - encode_repository_checked guarded on an_uncontained_target and NOTHING else,
    so a requirement whose locator is occupied by an authored source walked
    through and got PUBLISHED. It now refuses first, with its own cause:
    RepositoryEncodeTargetIsAuthoredSource. The two refusals stay separate
    because their remedies are -- one says fetch an object that is not here, the
    other says something IS here and is the wrong kind, which no fetch repairs.
  - loaded_closure_standing answered LoadPartial for every not-complete closure.
    LoadPartial promises "fetch the named identities and this completes", which
    is FALSE for an occupied locator. LoadStanding grows
    LoadRequiresNodeAtAuthoredSource, and standing_loaded answers false for it:
    the document read fine, but what it asserts is unsatisfiable.
  - The raw encoder spells a source-occupied target uncontained, which is
    indistinguishable from absent on the wire. Both callers now refuse before
    reaching it.

THE THIRD FINDING IS THE ONE I MOST NEEDED. My byte-equality claim --
one_closure_has_one_image_however_its_store_was_assembled -- catches a
DISAGREEMENT between assembly routes but not a wrong spelling both routes share.
Proven, not conceded: under an encoder that spells EVERY target uncontained,

  one_closure_has_one_image_however_its_store_was_assembled   PASS
  a_complete_closure_spells_its_child_contained               FAIL
  a_partial_closure_spells_its_missing_child_uncontained      PASS

Agreement is consistency, not correctness. The new claims read the arm out of
the document through decode_target_reference -- the module's own reader, not a
second parser written to grade the first.

Item 3 also lacked its positional control. Canonicalizing named children is
satisfied by sorting every child list unconditionally, which is wrong under
Cardinality and Instantiation where PositionalEdges makes position the meaning.
The canonicalizer already dispatches correctly on kind_edge_discipline; nothing
executed it, so the rule was true and unwitnessed -- and an unwitnessed rule is
one a later local sort can quietly replace.

SCM suite 317 pass, 0 fail.

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

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Correcting review 58316, which is an APPROVE resting on a description of the code that is no longer true. I would rather lose the approval than let the stated safety property stand, because a later reader would plan against it.

It says:

sole_constructor brands on SemanticNodeObjectRef / AuthoredSourceObjectRef mean a child edge cannot syntactically point at a file

That is not what this branch does, and an earlier revision of it tried exactly that and was wrong. StoredEdge.target is SemanticNodeTarget { locator: ObjectId } — a plain record, publicly constructible on purpose. A child edge absolutely can name a locator an authored source occupies. That state is writable, and every witness in this PR that exercises the wrong-kind path constructs it directly.

The branded-reference version was tried and reverted for a load-bearing reason: a partial closure must be able to reference an object it does not carry, so a branded target would have needed a mint that records a claim rather than evidence — an exported forge factory, which is precisely how a sole_constructor wall gets reopened by the helper written to prove it.

What the model actually guarantees is narrower and is the honest statement:

  • a child edge states a proposition — "a semantic node is required at locator L". Stating it forges nothing.
  • whether any store satisfies it is an observation, produced only by resolve_node_target → find_node_record, whose three arms separate absent / occupied by a file / found.
  • the wrong-kind state is therefore refused at every consumer that could act on it — partial admission, completeness, both closure encoders, encode_repository_checked, and loaded_closure_standing — not made unwritable.

So this class sits at §4b rung 3 (structurally guaranteed), not rung 4. Reporting it as rung 4 would be the rung inflation §4b(1) names, and the distinction is not academic: three real defects were found in this exact seam after the board first read ready: true, and every one of them was a consumer reading a two-valued answer where three facts existed. If the state were unwritable those defects could not have existed.

The one thing that is unwritable: a contained wire reference to a file. The encoder’s target index carries semantic nodes only, so there is no entry to find — that is construction, and a_complete_closure_spells_its_child_contained plus its uncontained control execute it.

No code change; the code is correct as written and the description was not. SCM suite 317 pass, 0 fail.

— sent from deep-carp-676

…habit

The SCM reviewer's exact-head ruling on 837115a listed five required closures.
54cd2e4 had already landed three of them (the repository source-occupied
refusal, LoadRequiresNodeAtAuthoredSource, and the contained-spelling witness
with the mutation receipt they asked for). These are the remaining two, plus the
part of the first that their reading was right about and mine was not.

encode_complete_closure_document asked unresolved_identities, which projects the
ABSENT population. For a requirement whose locator is occupied by an authored
source that population is EMPTY -- so the FRONT DOOR returned bytes for the
identical state partial admission refused at the side door. My previous claim
that "both entry points are closed" was true of admission and false of this one.

THE REPAIR IS A CARRIER, NOT A THIRD CHECK. Writing the judgment at each entry
point is what produced this bug twice: `.dag` has no module privacy, so
encode_closure_document stayed importable and the wrong-kind state stayed
representable through it no matter how many front doors were guarded. It now
takes a WellKindedClosure -- sole_constructor, mintable only through
admit_well_kinded_closure, which runs the census. There is no argument a caller
can build that carries the state, so the raw writer needs no claim of its own.

An ABSENT population is fine in that carrier and is the partial-closure case the
format exists for: it seals KIND, not completeness.
encode_admitted_partial_closure_document re-seals rather than re-judges, because
admit_partial_commit_closure already refused source_occupied before minting.

Also: unresolved_commit_root_step asked position_of, which is the GENERIC object
index, so a commit root pointing at a file found a position and passed. It asks
node_position_of now. Repository roots are established against node positions,
not object occupancy.

The seal is not decorative -- it broke three fixture call sites that had been
handing the raw encoder a store and a root, which is exactly the shape a
production caller could have used.

  the_complete_writer_refuses_a_wrong_kind_closure_and_names_it
  the_complete_writer_still_encodes_a_complete_closure          (control)

SCM suite 319 pass, 0 fail.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head re-review — 9bdd976f501a2b14c8d35840b6ec101e677cddd6

REQUEST_CHANGES

I reviewed the complete two-commit delta from 837115a through 54cd2e4 and 9bdd976.

The central repair is accepted. WellKindedClosure is the right option-2 boundary: it seals KIND rather than completeness, its only construction routes are the full-census mint and the re-seal of an already-admitted partial closure, and encode_closure_document now accepts only that carrier. This closes the raw closure writer without destroying the legitimate absent-target state. The complete writer now consumes the full census and names the whole wrong-kind population. The checked repository writer now consumes the table's source-occupied population. LoadRequiresNodeAtAuthoredSource is the right standing. The direct target-arm witnesses close item 4: forcing every target to uncontained takes a_complete_closure_spells_its_child_contained red while the genuinely partial control remains green.

Four blockers remain.

1. Checked repository commit-root encoding still collapses wrong-kind into missing

unresolved_commit_root_step now asks node_position_of, which correctly refuses to treat a file position as a node position. But its Absent arm immediately returns the locator to RepositoryCommitRootNotInStore. It never asks whether the generic object index DOES contain that locator.

So this live residual is still misclassified:

  • a SemanticNodeObjectRef was honestly minted against store A;
  • a repository is rebuilt around store B, which holds an authored source at the same locator (the cross-store wrong-kind/collision state the reference comments explicitly preserve);
  • the commit root is the only requirement at that locator, so the table-edge source-occupied scan does not see it;
  • node_position_of is absent, while position_of is present;
  • checked encode reports RepositoryCommitRootNotInStore for an object that is present and wrong-kind.

That is the same absent/wrong-kind conflation one level later. Preserve the three-way result at commit-root encoding: node position / no object / authored-source position. Add an encode-side wrong-kind cause (preferably RepositoryCommitRootIsAuthoredSource) and a discriminating witness paired with the genuinely absent-root control. A source at the root locator must never be reported as missing.

2. The new loaded standing has an implementation but no discriminating execution

loaded_closure_standing now reads both census populations, but the witness changes only add the new arm to imports, tags, and exhaustive matches. No claim drives a decoded source-occupied closure through closure_document_standing or loaded_closure_standing.

Execute the exact document state this arm exists for: carry an authored-source entry at L, then a node whose uncontained target names L, and root the document at that node. Decode must succeed; its standing must be exactly LoadRequiresNodeAtAuthoredSource, standing_loaded must be false, and it must never authorize supersession. The one-fact absent control remains LoadPartial; the complete control remains LoadComplete. Mutating the source-occupied branch back into absent/LoadPartial should take only the new claim red.

Without that, the new arm is currently specification plus exhaustive plumbing, not executed behavior.

3. The future-manifest identity-version statement is still unchanged

repository_object_identity_rule_tag = "scm-object-identity-v1" is correct for the current node-plus-source total law. The adjacent statement that a future manifest branch “joins this same tag when it lands” is not.

The manifest arm should join the SAME identity authority, not mint a peer authority, but adding an object arm and derivation changes that authority's total rule and must advance its version. Rewrite the statement to say exactly that; do not pre-bump the current tag for a branch that does not exist yet.

4. Item 8 still needs the exact-head narrative/source sweep

The PR body is still the 837115a account: it attributes raw unwriteability to the two indexes rather than the new WellKindedClosure carrier, reports 313/0, and omits the later standing and root-boundary repairs.

Several current-source statements remain false or stale:

  • commit_closure_json_v2 still calls the authored-source addition “a strict extension, not a format version,” despite the v3 cut and frozen-v2 refusal.
  • object_store still says every node-requiring position takes the sealed node brand and a file in a child edge is unwritable; canonical edges now carry public SemanticNodeTarget propositions and wrong-kind is a census/refusal state.
  • repository_envelope says dependency-order emission is insertion order; the accurate statement is derived post-order with insertion-history tie order among independent components. It also still says “THIS FABRICATES NO v2” beside the live v2 encoder.
  • supersession still audits loaded_closure_standing as producing only LoadComplete | LoadPartial; it now also produces the new wrong-kind standing.

The comment saying the partial-admission front door remains mechanically bypassable should stay: WellKindedClosure seals kind, not partial-write policy, and its public mint intentionally admits absent targets. That comment describes a different, still-honest rung.

Rename one_closure_has_one_image_however_its_store_was_assembled to the exact claim it proves, for example:

a_completed_parent_first_graft_has_the_naturally_built_image

That names the two exercised assembly routes without asserting global serialization canonicality for independently ordered components.

Exact-head GitHub CI was still in progress when this review was submitted. Fresh green CI remains required after the repairs above. No merge authorization carries on 9bdd976.

… new standing

Four blockers from the SCM reviewer's exact-head ruling on 9bdd976.

1. COMMIT ROOT. Asking node_position_of was the right first question, but every
   node-index miss became RepositoryCommitRootNotInStore -- reporting ABSENCE for
   an object that is present and wrong-kind, the same conflation one layer later.
   commit_root_standing now answers three ways and the encoder grew
   RepositoryEncodeCommitRootIsAuthoredSource.

   HONESTY NOTE ON ITS EVIDENCE, because the arm ships without a discriminating
   RED and that must not pass silently. I could not author one. RepositoryCommit.root
   is a SemanticNodeObjectRef, sole_constructor, obtainable only by storing a node;
   reaching this arm needs a store where that same LOCATOR holds a file, which is a
   64-bit digest collision. No fixture can construct it without the forge factory
   this module refuses to export. Per DESIGN section 4b the arm is therefore
   mitigatable-with-unauthorable-RED, and its next-rung trigger is a fixture
   capability that can seed a store at a chosen locator. I am not claiming coverage
   for it.

2. THE NEW STANDING IS NOW EXECUTED. LoadRequiresNodeAtAuthoredSource had an
   implementation, exhaustive plumbing, and no evidence any external document
   reaches it rather than LoadPartial -- a typed carrier and a tag are not a
   consumer. The document is authored BY HAND because the encoders now refuse to
   produce it, which is exactly section 4b's case for authoring a RED as source
   handed to the decoder.

     scm_image_a_requirement_over_a_carried_file_is_not_partial
     scm_image_a_requirement_over_nothing_is_partial_and_loaded   (control)

   Mutation, the one the reviewer named: routing source_occupied back through the
   absent branch takes the first red and leaves the control green.

3. THE MANIFEST TRANSITION RULE was wrong. It said a manifest branch "joins this
   same tag; it does not mint a third one". The first half is right, the second
   does not follow: joining one authority is the section 3 point, but adding an arm
   changes the authority's TOTAL LAW, and this tag names the law. The tag is not
   pre-bumped for a branch that does not exist; the rule is corrected so the next
   author does not read it as permission to grow the law under a fixed name.

4. TWO SOURCE COMMENTS ASSERTED THINGS THAT ARE FALSE.

   object_store said "every node-requiring position takes the node brand; a file in
   a child edge is then not refused, it is UNWRITABLE." That was true of a design
   this module no longer has. It is the worst kind of stale comment -- one asserting
   a SAFETY property, which a reader plans against -- and an external review restated
   it back as the module's guarantee, which is how a false comment becomes a false
   shared premise. The honest statement is rung 3, not rung 4: the state is writable
   and every consumer refuses it by name. Three real defects were found in this seam
   AFTER the brands landed; had it been unwritable, none could have existed.

   commit_closure_json_v2 still called the schema addition "a strict extension, not
   a format version". The module's own policy refutes it -- a schema that grows a
   member grows a new format tag -- and this codec emits v3 and refuses the frozen
   predecessor outright.

SCM suite 321 pass, 0 fail.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head re-review — 103f2d529075eed47d4f8c07e720e795854faf46

REQUEST_CHANGES

The functional commit-root repair is correct. commit_root_standing now distinguishes node-present, object-absent, and authored-source-present; the checked encoder no longer reports wrong-kind occupancy as RepositoryCommitRootNotInStore.

Ruling on the unauthorable RED

Keep RepositoryEncodeCommitRootIsAuthoredSource.

I accept the declared mitigatable/unwitnessed disposition. Reaching it requires a SemanticNodeObjectRef honestly minted against one store and a second store holding an authored source at the same 64-bit locator. Without a real cross-kind hash collision, the only way to manufacture that state is a chosen-locator store forge. Exporting such a capability solely to exercise the backstop would weaken the construction wall the test is supposed to protect. Removing the arm would instead knowingly collapse a mathematically admitted wrong-kind state into absence.

Do not add a forge factory and do not count the arm as executed. The source/PR declaration and next-rung trigger are the right evidence ceiling.

The hand-authored source-occupied closure document is also the right producer-side witness, and the future-manifest identity-rule statement is corrected.

Four small but blocking items remain.

1. Carry the new standing to the real supersession destination

scm_image_a_requirement_over_a_carried_file_is_not_partial proves:

  • decode reaches LoadRequiresNodeAtAuthoredSource;
  • standing_loaded answers false.

It does not execute the downstream consequence required by the prior bar: the exact standing produced from that exact document never reaches supersede_head_slot. The existing supersession witness merely enumerates the new arm in exhaustive matches.

Pass the standing returned by closure_document_standing(decode_closure_document(doc)) through observe_generation and supersede_head_slot, with every non-standing precondition held at its admitting value. Require StandingDoesNotPermit { standing: LoadRequiresNodeAtAuthoredSource } (or an equally exact refusal), not only a false Boolean.

Also complete the one-fact third cell: a semantic node carried at L, referenced through the same uncontained spelling, must yield LoadComplete and standing_loaded == true. The current source and absent cells do not execute that join.

2. Narrow the item-4 consistency claim name

The old name still stands:

one_closure_has_one_image_however_its_store_was_assembled

It overclaims global byte canonicality while the repository authority explicitly permits independent-object insertion history to change positions. Rename it to:

a_completed_parent_first_graft_has_the_naturally_built_image

That is exactly what the fixture compares. Update the adjacent prose and PR mutation table with it.

3. Finish the authority-text sweep

The two comments repaired in this commit were real, but the prior exact-head sweep was broader. Current false present-tense statements still include:

  • repository_envelope: store_records_in_dependency_order is called insertion order. It now derives dependency post-order; insertion history survives only as tie order among independent components.
  • repository_envelope: “THIS FABRICATES NO v2” beside the live v2 codec.
  • repository_envelope: the encode-refusal commentary says there is one producible cause and later exactly three unrepresentable cases, while the coproduct has several—including the explicitly unwitnessed commit-root arm.
  • scm_repository_envelope_witness_test: the section says “one claim per cause” and “the three causes,” contradicting the declared unauthorable arm.
  • commit_closure_json_v2: the two-index commentary still says the raw encoder accepts any store and that index splitting alone closes the raw route; it now takes WellKindedClosure.
  • commit_closure_json_v2: “A missing position is UNREACHABLE” is false for an admitted partial closure. Absence is the deliberate uncontained route; only a missing position for a target the document claims to contain through complete dependency ordering is an ordering defect.
  • supersession: the producer audit says loaded_closure_standing returns only Complete/Partial and says repository_envelope classifies nothing into LoadStanding. Both are now false; repository_envelope_load_standing is a live producer too.

These are authority comments, not cosmetic wording. Reconcile or delete them in one sweep so the next review does not discover the same moved premise one paragraph at a time.

4. Exact-head CI

GitHub workflow run 33571356933 is still in progress. Approval requires its complete exact-head green result after the changes above.

The remaining bar is therefore one downstream witness, one exact control, one claim rename, the already-enumerated authority cleanup, and terminal exact-head CI. No new object-model redesign is requested.

gunbc-ci-auto-heal and others added 2 commits September 2, 2026 00:14
CI red on 103f2d5, reproduced locally rather than assumed flaky:

  nfr_roster_receipt: unrostered=1 stale=0 live=187
  unrostered: ["gunbc.scm.repository_envelope::unresolved_commit_root_step"]

The new fold carried `_ => found` over CommitRootStanding, a closed coproduct.
The roster exists to make exactly that visible, and the lens caught it on the
first commit that introduced one.

There are two ways to satisfy it and only one is right. Rostering the wildcard
would record a residue that does not need to exist; the arms are enumerable, so
the fold is written out. That also restores what the coproduct is for: a fourth
standing would now break this match rather than being silently absorbed by the
wildcard into "keep whatever we had" -- which, for a fold whose whole job is to
report the FIRST bad root, would have quietly kept reporting the wrong one.

  nfr_roster_receipt  1 passed
  SCM suite           321 pass, 0 fail

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…he stale text

Three of the four items from the SCM reviewer's ruling on 103f2d5. The fourth is
terminal exact-head CI, which follows this push. They accepted the unwitnessed
commit-root arm's disposition: keep it, export no forge, claim no coverage.

1. THE NEW STANDING NOW REACHES AN ACTUATOR. The hand-authored document proved
   LoadRequiresNodeAtAuthoredSource is produced; nothing proved it CHANGED
   anything. A standing no actuator consumes is a value with a name and no
   consequence, and the exhaustive arm in supersession was plumbing, not
   execution. The claim now drives the standing THAT DOCUMENT ACTUALLY PRODUCES
   -- not a literal handed to the actuator -- through observe_generation into
   supersede_head_slot with every other condition admitting, so the only thing
   that can refuse is the standing, and requires exactly
   StandingDoesNotPermit { standing: LoadRequiresNodeAtAuthoredSource }.

   The third one-fact cell was also missing: a SEMANTIC NODE carried at that
   locator must resolve the SAME uncontained spelling to LoadComplete. Without
   it the pair is about presence rather than kind, and satisfiable by a decoder
   that treats every uncontained target as unresolved -- which would make
   LoadComplete unreachable through that spelling.

2. THE IMAGE CLAIM IS RENAMED to a_completed_parent_first_graft_has_the_naturally_
   built_image. The old name claimed GLOBAL image invariance across every
   assembly history -- more than the fixture builds and more than the repository
   authority grants, since insertion history still decides tie order among
   INDEPENDENT components. A claim whose name promises more than its body
   executes is the same inflation as a rung reported above its evidence.

3. THE STALE AUTHORITY TEXT IS ONE SWEEP, not another round of single-paragraph
   repairs, because that is how the previous two got missed:
   - repository_envelope said emission order is INSERTION order. It is a derived
     post-order walk; what survives is the narrower true claim that insertion
     history decides TIE ORDER among independent components.
   - "THIS FABRICATES NO v2" sat above an encoder emitting v2. The number moved
     and the prose did not; it is the v3 statement now.
   - The repository encode header and its witness header both said "the three
     causes" / "one claim per cause". The coproduct is larger and one cause is
     deliberately unwitnessed, so both now say which one and why.
   - commit_closure_json_v2 credited the index split alone with closing the raw
     route. It never did -- the carrier does. Both facts are needed and the note
     now says which does what.
   - "A missing position is UNREACHABLE" was true only while every closure was
     complete. For an ADMITTED PARTIAL closure a missing node position IS the
     uncontained arm, and reading it as unreachable would invite an author to
     delete the format's reason for existing.
   - supersession's producer census said loaded_closure_standing answers "only
     LoadComplete or LoadPartial", and that repository_envelope classifies
     NOTHING into LoadStanding. Both premises are obsolete; a stale count is
     exactly what corrupts a census. The conclusion is unchanged and broader.

SCM suite 323 pass, 0 fail.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head re-review — 689a2b5b56cf2cd59e12cd358eb2ee46d076acf5

REQUEST_CHANGES

The functional bar is closed. I accept the document-produced standing reaching the exact StandingDoesNotPermit { standing: LoadRequiresNodeAtAuthoredSource } actuator arm, the carried-node LoadComplete control, the scoped graft-image claim name, the commit-root three-way implementation with its explicitly unwitnessed collision arm, current-main ancestry, and terminal exact-head CI run 33577206580.

What remains is the authority-text reconciliation this delta says it completed. The sweep is still internally contradictory in four exact places.

1. The supersession census still carries the stale total

The audit still begins:

PRODUCERS, TOTAL: two, both in gunbc.scm.commit_closure_json_v2

but the same paragraph now correctly says repository_envelope_load_standing is another producer and that the producer count grew. A census cannot retain the old total beside its own correction. Recompute and state the actual denominator, or narrow and name a different denominator such as the permitting producer population; do not leave “total two” beside “the producer count grew.”

2. repository_envelope still describes an earlier encode coproduct

Above RepositoryEncodeRefusal, the current text still says “The single producible cause” and “the two sibling arms” beside an eight-arm coproduct.

Above encode_repository_checked, it still says:

The three ways a repository can be unrepresentable are exactly the three arms below

while the implementation separately adjudicates wrong-kind table targets, absent roots, wrong-kind roots, checkout, duplicate references, allocator range, and parent order.

Rewrite these as the current population. Preserve the deliberate no-RED disposition of RepositoryEncodeCommitRootIsAuthoredSource; do not restore a fictional one-claim-per-cause tally.

3. The witness header still asserts and retracts the same fact

scm_repository_envelope_witness_test.dag still opens the section with:

THE CHECKED ENCODER, with one claim per cause

and immediately says the roster is no longer one claim per cause. Make the heading truthful directly. The following explanation of the unauthorable collision arm is good and should remain.

4. The PR receipt still double-counts the renamed claim and overstates the dispositions

The mutation table lists both the retired one_closure_has_one_image_however_its_store_was_assembled name and a_completed_parent_first_graft_has_the_naturally_built_image as separate rows. This was one claim renamed to narrow its assertion; replace the old row rather than counting both.

The body also says every consumer “refuses it by name,” then includes completeness and both closure writers. closure_is_complete answers false; the sealed raw/admitted writers cannot receive the state; the complete writer and admission surfaces return named outcomes. State those different guarantees as different guarantees.

Final bar

  1. Correct the four exact source-text contradictions above in one pass.
  2. Correct the PR receipt table and disposition sentence.
  3. Obtain terminal exact-head CI after the .dag comment edits.

No further model or witness redesign is requested. No approval carries on 689a2b5.

gunbc-ci-auto-heal and others added 6 commits September 2, 2026 03:15
…en against

The reviewer's hold on 689a2b5 was text-only but authority-bearing: three
source notes described populations their own subjects had outgrown, and one
asserted a rule in the present tense directly above its retraction.

The supersession census is recomputed from the live tree rather than patched.
It said "PRODUCERS, TOTAL: two" beside "the producer count grew"; both cannot
hold at the declared denominator. There are five, across three modules.

Recounting surfaced a second, load-bearing falsehood the review did not name.
The census also claimed closure_document_load_standing was the ONLY declaration
able to return LoadDocumentMalformed, and concluded that the producer count grew
while the permitting surface did not. LoadDocumentMalformed is the sole
permitting standing, and repository_envelope_load_standing returns it for its
member-read and target-reference modes while object_table_load_standing returns
it for six of its seven causes. The permitting surface grew with the producer
count. Recording the growth of one while denying the other is exactly the stale
count this census exists to catch, so the conclusion is replaced rather than the
number. What survives is stated narrowly: the success path stays outside the
relation by construction, because none of loaded_closure_standing's three arms
permits.

RepositoryEncodeRefusal's note described "the single producible cause" and "the
two sibling arms" above an eight-arm coproduct, and the checked encoder's note
named three ways a repository can be unrepresentable above the same eight. Both
now enumerate what exists, grouped by remedy, keeping the domain rule that was
each paragraph's actual content.

The witness header opened by asserting one claim per cause and retracted it two
lines later. The heading now carries the exception it always had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
The recount stated its denominator as "declarations whose return type is
LoadStanding". That is too wide: witness modules declare their own, and
test.claim.scm_commit_closure_json_v2_witness scm_image_standing_of is one. A
fixture helper is not a producer this destination can be reached from, so the
denominator is now production classifiers under gunbc.scm.*, and the witness
helper is excluded by name rather than by being overlooked.

The withdrawn claim -- that the producer count grew while the permitting surface
did not -- is removed rather than qualified. The account states positively what
holds: LoadDocumentMalformed is the one permitting standing, three of the five
producers can answer with it, and no declaration or module owns it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
main carries an updated `proof` row in gunbc.rust_source_type_bindings whose
committed projection was never regenerated, so origin/main is itself in a
drifted state and every branch that merges it inherits a failing regen phase.
Measured here rather than assumed: both the .dag authority and the .rs
projection on this branch are byte-identical to origin/main, and regen still
reports first_generation_equal=false for this one file.

The fix is the projection, not the authority: the generated file now carries
what the row says. The build lane is green at this head -- regen
first_generation_equal=true, generated-artifact 35/35 matched, 0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
The census repair left its neighbours stating the population as it stood when
each sentence was written. Two of those neighbours are claims, and that is the
part that matters: their names quantify universally while their bodies
enumerated a subset, so the standing this branch introduces could have collapsed
into the permitting arm with both green.

every_non_permitting_standing_refuses_and_says_which checked four of the five
non-permitting standings. malformed_is_the_only_standing_that_permits_replacement
checked two of the three it owns (LoadComplete and LoadPartial belong to
a_partial_load_is_never_supersedable, so the five are covered between the pair).
Both now check the whole population.

Executed rather than argued: routing LoadRequiresNodeAtAuthoredSource to the
permitting arm takes BOTH claims red, and both are green with the arm restored.
Before this change that mutation was invisible to both.

The prose repairs are the same defect without the teeth. load_standing said
"four refusals ... for four different reasons" and listed four of five, so the
new standing had no stated reason; it has one now, and it is its own -- the
document decoded and what it asserts is unsatisfiable, so no fetch repairs it
and superseding would destroy a generation over a defect not in its bytes.
object_table_json called collision "the five"; it is the seventh cause against
the other six. The census said "one producer today" after counting five, and
"a sixth standing" above a six-arm coproduct.

The census partition is restated at the grain the review asked for: three
declarations DECIDE LoadDocumentMalformed from a refusal cause, one RELAYS it
without deciding, and one cannot return it -- a fourth return route is not a
fourth decision authority. The surviving consequence is stated at arm grain
(the ClosureDocLoaded branch) rather than as "no load that succeeded", because
this module deliberately separates a document that decoded from one whose
standing answers standing_loaded true.

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

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exact-head native ruling — aec3deb7cfe61a631c2f9207e3079f45d6f194eb

APPROVE

This native review mirrors the dashboard ruling recorded as review 58646 and supersedes my earlier REQUEST_CHANGES reviews for the SCM object-model content.

The complete bar is closed at this exact SHA: the object/carrier model, wrong-kind publication walls, commit-root three-way result and explicitly unwitnessed collision backstop, standing-to-supersession execution, carried-node control, canonical equality, post-order encoding, protocol versioning, frozen predecessor evidence, durable source round trips, mutation evidence, scoped census, eight-arm encode-refusal prose, witness exception, PR receipt, and disposition split are accepted.

Workflow 33620909083 is terminally green on this exact head: required-witnesses-build, required-witnesses-floor, rust-unit-tests, fabric-evidence, and the aggregate witnesses job all succeeded.

This is an exact-head content approval. Since this commit composed main@583ffd661c0037c0f3a10a8410d4cbeb5b4c5c07, and main has subsequently advanced, it does not by itself waive the standing current-main composition/regeneration requirement or authorize landing a different SHA. No substantive SCM redesign remains.

@gunbai-bot
gunbai-bot Bot merged commit 2fae872 into main Sep 2, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the scm-object-model branch September 2, 2026 12:28
gunbai-bot Bot added a commit that referenced this pull request Sep 2, 2026
…del under it (#10095)

The plan listed the pre-#9891 add/commit recipe under "Settled by execution, so
not open questions any more". #9891 replaced the identity model underneath it, so
the recipe is wrong at exactly the boundary add/commit needs, and it sat on main
saying otherwise. Found by review on gunbc#10069; text-only, no rollback and no
redesign.

Why it matters more than a stale paragraph: a plan section that says "settled by
execution" is read as an instruction. The next author to pick up add/commit would
have followed a recipe that cannot typecheck, and would have discovered that only
after building against it.

What #9891 changed, verified against the source rather than restated:

  - authored source is its OWN object arm, not a semantic node wearing a node
    costume -- a file has somewhere to live that is not a synthetic Node, which
    is what that change was for;
  - a semantic-node reference and an authored-source reference are DISTINCT, so
    "rebuild the same node and re-derive its ObjectId" no longer identifies one
    thing;
  - mint_repository_commit takes `root: SemanticNodeTarget`
    (gunbc.scm.repository_envelope), not a bare ObjectId. Handing it an
    authored-source identity does not typecheck -- it is not a runtime refusal
    to be checked for.

The recipe is kept as the reasoning that led to the replacement rather than
deleted, under a heading that says what it is. The current boundary is stated in
its place: add/commit remain blocked on a CorpusManifestObject
(path -> semantic_root -> authored_source_identity) plus a staging authority.
Immediate object-store insertion is NOT the add model -- add has nowhere to
persist what is staged until that authority exists. ScmWriteOutcome is still the
right shape for the verb's result and is not what blocks the verbs.


Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 4, 2026
… a projection

The governing sentence is theirs, and it is a better model than either arm of the
fork I put to them: a repository commit freezes an authored corpus manifest, and a
semantic program is a separately bound ingestion projection of that corpus rather
than an alternative kind of commit subject.

Three of my positions are corrected by it.

semantic_root does not belong in the manifest, and the reason is deeper than
today's constructibility: it is a DERIVED ingestion result depending on corpus
context, imports, the ingestion rule and language versions. Putting it there fuses
an input with a realization result and makes the manifest unauthorable for
malformed or not-yet-ingested source -- exactly the corpora an SCM must freeze.

commit must NOT refuse for want of ingestion, which was my proposal. Freezing what
was authored is fully answerable now; only SEMANTIC questions lack an answer, and
those refuse with a typed projection-unavailable cause. Withholding commit would
fuse recording state with establishing semantic validity, which this design keeps
apart -- an invalid corpus must still be committable if commit is not certification.

And the manifest joins ScmObject without becoming a StoredEdge target, because
store membership and edge admissibility are separate axes. That is what lets C
preserve #9891 rather than reopen it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
gunbai-bot Bot added a commit that referenced this pull request Sep 5, 2026
…never in it (#10445)

* Design note: measure the ingest path instead of asserting it, and stop before minting a second SourceRef

Two corrections, both from measuring rather than searching.

Ingestion is MODELED, not absent. My first draft searched dag/gunbc/scm, found
nothing, and let an empty subtree speak for the repository -- a partial observer
returning the negative value of a total observer, which is the class this branch's
own roster ledger records.

The manifest vocabulary largely EXISTS: v2.compiler.source_authority declares
SourceRef { path, source_root, content_hash } and SourceRootIngest, with a build
function. Minting a corpus manifest of path -> content identity would be a second
name for most of SourceRef -- the section 3 violation, proposed while citing
section 3 as the reason for the branch that just merged.

And the measurement that decides the slice: v2.test.program_assembly.real_ingest
fails at main on two claims, both DECLARED in floor_expected_red. So scm cannot
obtain a program root from files today -- the machinery is present and red, not
missing. commit is therefore not built in this slice; add and status are.

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

* Record the reviewer's ruling: a commit freezes a corpus, a program is a projection

The governing sentence is theirs, and it is a better model than either arm of the
fork I put to them: a repository commit freezes an authored corpus manifest, and a
semantic program is a separately bound ingestion projection of that corpus rather
than an alternative kind of commit subject.

Three of my positions are corrected by it.

semantic_root does not belong in the manifest, and the reason is deeper than
today's constructibility: it is a DERIVED ingestion result depending on corpus
context, imports, the ingestion rule and language versions. Putting it there fuses
an input with a realization result and makes the manifest unauthorable for
malformed or not-yet-ingested source -- exactly the corpora an SCM must freeze.

commit must NOT refuse for want of ingestion, which was my proposal. Freezing what
was authored is fully answerable now; only SEMANTIC questions lack an answer, and
those refuse with a typed projection-unavailable cause. Withholding commit would
fuse recording state with establishing semantic validity, which this design keeps
apart -- an invalid corpus must still be committable if commit is not certification.

And the manifest joins ScmObject without becoming a StoredEdge target, because
store membership and edge admissibility are separate axes. That is what lets C
preserve #9891 rather than reopen it.

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

* A manifest at a semantic requirement is its own wrong-kind fact

ScmObject gains a corpus-manifest arm, which forces every exhaustive match
over it to answer. Two of those answers deserve the record kept in the design
note: refusing whenever a manifest exists anywhere in the store makes
"serialize the closure rooted at P" fail on unrelated repository inventory --
a manifest is wrong AT a SemanticNodeTarget, not wrong IN an ObjectStore --
and bumping the closure format tag would announce a protocol widening that
did not happen.

So the census gains a THIRD population rather than folding manifests into
source_occupied. It exists for the same reason source_occupied does: "no
object is at L" and "an authored source is at L" have incompatible remedies,
and a manifest at L is a third such state. Folding it in would report a
manifest as a file -- a well-formed answer of the right type and the wrong
meaning, which is the class this module's history is made of.

Both admissions therefore rename their wrong-kind arm and carry BOTH
populations in one arm. Splitting by class would report only whichever was
tested first, so a caller repairing one and retrying would discover the other
a round later -- the objection this module already records one level up
against naming the first identity instead of the population.

Beside that, in object_store: the dependency walk follows a manifest's
entries by AuthoredSourceTarget, and objects_equal becomes a full 3x3 whose
manifest arm compares structurally rather than by digest -- both objects are
already under one locator by the time it is asked, so a digest comparison
would answer true by construction and pass the 64-bit collision the arm
exists to catch.

The remaining B'-cut sites -- narrowing the admitted carrier to the
root-reachable population so the encoder has no manifest arm to write -- are
the four errors claim_batch now names, and are blocked on a ruling: nothing
in the model can reach an authored source from a semantic root, so a strictly
reachable closure would silently narrow the v3 wire language while holding
its tag constant.

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

* The authored sources in a commit closure were never reachable from its root

Every traversable edge from a SemanticNodeTarget is again a SemanticNodeTarget
-- an ObjectRecord carries a kind and children and nothing else -- so there is
no typed path from a commit root to an authored source. The sources that
appeared in these documents were repository inventory arriving through an
encoder that serialized the whole store, not content of the closure the
document names. Once a corpus manifest can sit in that store, the same encoder
acquires an arm it has no honest way to write, because it has no refusal
channel.

So derive_semantic_closure becomes the one authority and its output population
is nodes only. WellKindedClosure carries that population instead of the store
it came from, which is what makes the narrowing structural rather than
checked: the encoder's parameter can no longer name a manifest, so there is no
arm to write well or badly. Both admissions mint through one well_kinded_image
-- two independent walks would be the authority substitution this carrier
exists to remove, and it would have failed silently, with ordinary admission
narrowed while partial admission still shipped the whole repository.
well_kinded_store loses its name with its shape; a consumer that genuinely
needs an ObjectStore should have to say so.

The census follows the same walk, so it stops counting requirements of objects
the root cannot reach and stops counting the root's own twice. Its visited set
covers absent and wrong-kind targets too, not just admitted nodes: keying on
admitted nodes alone would re-classify a shared child once per parent, so a
diamond over one absent object would report it twice.

THE FORMAT TAGS MOVE, IN OPPOSITE DIRECTIONS, ON THEIR OWN AXES.

The closure goes to v4. Its writer can no longer emit a {source} entry and its
reader now refuses one, which is a materially NARROWER language under the same
protocol -- and holding v3 while the emitted language shrinks is the same false
version claim as holding it while the language grows, run backwards. The
closure's table decode is its own step rather than a flag on the mixed one,
because a shared step with a leniency switch puts two vocabularies back into
one authority and makes the narrower one responsible for remembering to be
strict.

The repository goes to v3, and its identity rule to v2, because the mixed
object table gained a {manifest} arm and a manifest is a third identity
family. Encoding that arm without decoding it would have been an asymmetric
format, so both land together: entry sources take both target arms exactly as
edges do -- a repository may hold a manifest whose sources it does not -- but
resolve through the whole-object index rather than the node index, since the
required kind differs, and the decoder re-checks the kind it resolved rather
than trusting the position. An empty path refuses rather than being cast.

The manifest member is read BEFORE the source-or-node half and dispatches only
on MemberReadMissing, so a malformed {manifest} cannot arrive there as "not a
manifest" and be re-reported as a missing kind -- the collapse this decoder's
own history records one member up.

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

* The four witnesses that decide the reachability cut, and one that moves house

Each of the four goes red against a different wrong answer that was actually
available. Without the two controls, a derivation that refused every store
containing anything unrelated would satisfy the RED. Without the RED, a
derivation that admitted everything would satisfy the controls. Without the
parity claim, the two admission routes could disagree and only the unused one
be wrong -- which is the failure with no symptom, since admitted_well_kinded
built its carrier straight from the stored closure while the front door ran
the census.

The RED asserts more than a refusal. Both wrong answers were well-formed: put
a manifest in `absent`, which says "fetch this and the closure completes"
about a locator already occupied and repairable by no fetch; or fold it into
`source_occupied`, which reports a manifest as a file. So it requires the
manifest population to name it AND the other two to be empty. A non-emptiness
check would have passed either way.

The controls pin the POPULATION, not the outcome. Asserting only that
admission succeeded goes green for a carrier still holding the whole store.

AND ONE CLAIM CHANGES SUBJECT RATHER THAN CHANGING BEHAVIOUR. That an authored
source survives storage was being asserted through the commit-closure codec,
which is precisely the mis-homing this branch corrects: the closure cannot
reach a source, so it was never evidence about closures. Its sibling now sits
in the repository witness and answers the opposite question about the same
shape of store --

  semantic closure encode   the source is ABSENT
  repository encode         the source is PRESENT

-- which makes the authority split executable instead of described. Route both
writers through one population projection and exactly one of the pair goes
red. The repository half inspects the content arm and the derived identity
rather than an object count: a count is satisfied by any object of any kind at
any identity, so source-to-node substitution would stay numerically correct.
The existing repository round trips do not discharge it, because their fixture
holds only semantic nodes -- an encoder dropping every source would leave all
of them green.

Beside those, the third find arm reaches the three consumers that resolve a
semantic requirement: checkout, the repository commit mint and its pending
resolution, and role-requirement integration. Each names the manifest for what
it found rather than reporting it as a file or an absence.

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

* Put the object-table vocabulary where the object table is

The mixed decoder was producing ClosureDocRefusal values. Moving it to
repository grain without this would have relocated the implementation and left
its meaning owned by the layer it no longer belongs to -- the same upward
borrowing one level down, and an import arrow pointing both ways.

So the vocabulary goes down first. ObjectTableDecodeRefusal becomes the
complete owner of what a mixed table can refuse: member reads, unexpected
members, target references, the entry-shape and wrong-kind causes it already
had. ObjectTableMemberContext takes the eight entry shapes out of
ClosureDocMemberContext. The node-entry, kind and edge shapes come too, and not
because there are two consumers now -- because they are literally one schema in
both tables, so a copy per consumer would give one schema two authorities,
which is the fork this closes rather than a symmetry worth keeping.

ClosureDocMemberContext is then left with one arm, which is not a coproduct. A
discriminator that can hold exactly one value discriminates nothing and would
be one fact in two places, so the field goes and ClosureDocUnexpectedMember
carries only its key. It returns when a second closure member set exists.

The closure now wraps once, where the table's result reaches its envelope,
rather than supplying the vocabulary the table refuses in.

A DECLARED TRIGGER FIRED ON THE WAY, AND SAYING SO IS THE POINT OF HAVING
DECLARED IT. object_table_json carried a paragraph explaining why the wall
could not be built: decode_object_step's failure population was genuinely
mixed, so narrowing its result would have made the type claim an owner for
causes it did not own. Its stated next-rung trigger was a decode_object_table
whose member-read and unexpected-member causes are owned elsewhere. That is
exactly what this cut produces, so the paragraph now records the climb, what
the narrowing bought, and the residue that is left -- the collapse is still
writable one layer up by a consumer binding the wrapper bare, whose honest
trigger is module privacy in .dag.

AND ONE DECISION STOPPED HAVING THREE OWNERS. Classifying a TargetReferenceRefusal
into a LoadStanding was written inline in the closure codec twice, and the
object table was about to become the third. They agreed only because nobody had
yet had a reason to disagree. target_reference_load_standing now lives with the
type it classifies, per the rule that the authority owning the causes owns the
standing decision, and both consumers delegate to it.

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

* Move the object-table codec to the object table

A move, not a change: no document's bytes, acceptance, refusal standing or
remedy differs. What moves is the authority. The vocabulary went down in the
previous commit precisely so this one could be read as a relocation.

The codec was homed in commit_closure_json_v2 because that was once its only
consumer, and it has not been for some time -- repository_envelope imported the
accumulators, the position helpers and the table encode and decode upward from
a module whose own header says the repository is the layer above it. The v4 cut
made the split undeniable: a commit-closure table is NODES ONLY, a repository's
is a mixed population of nodes, authored sources and corpus manifests, and one
module cannot be the authority for two closed entry vocabularies without one of
them being a guest.

THE ENTRY SCHEMAS GO WHOLE, INCLUDING THE NODE ENTRY. That is the part worth
defending, because it looks like over-reach and is the opposite. The test is
whether a shape could exist in a repository object table with no closure
document anywhere near it, and {kind, children}, the kind variants and the edge
variants are literally ONE schema in both tables. Leaving a copy behind would
have given one schema two authorities -- the fork the move exists to close, not
a symmetry worth preserving.

What stays with the closure is what is genuinely its own: the format tag, the
envelope member set, the root reference, the node-only table walk, and the
refusal type that wraps the table's at that boundary. The file goes from 1690
lines to 669.

The import that motivated all of this now points the right way. The repository
takes its table machinery from the table's module, and takes from the closure
only the closure's own refusal type and classifier -- because a repository
document embeds a closure document and has to be able to say so.

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

* Name the format tag rather than copying it, and give the repository its own table refusal

TWO DEFECTS THE FORMAT MOVE EXPOSED, ONE OF THEM MINE FROM THIS BRANCH.

A TRANSCRIBED CONSTANT THAT ROTTED INTO A GREEN ASSERTION. Five hand-authored
fixtures carried the literal "gunbc-scm-commit-closure-v3". Bumping to v4 turned
the four asserting a successful load red, which is visible and fine. It also
turned the refusal claims beside them green FOR THE WRONG REASON:
an_unknown_connective_tag_refuses refused because the format tag was
unrecognized, never reaching the connective at all. That is DESIGN section 6's
rule about naming the instrument rather than copying its output, and the copy
was two lines from the authority. All five now reference
closure_document_format_tag, so the next bump cannot repeat it. The
frozen-predecessor claims keep their literal, because there the constant IS the
subject.

THE REPOSITORY WAS BLAMING THE CLOSURE FOR ITS OWN TABLE. A malformed object
entry in a repository's own table refused as RepositoryDecodeClosureDocRefusal
-- reported to an operator as the embedded closure document being malformed,
for a document whose closure was never read. This file already carries a
witness for that exact class one layer down, written when the member reader got
its own authority; it survived here only because the table's causes were still
spelled in the closure's vocabulary. RepositoryDecodeObjectTableRefused now
delegates to object_table_load_standing, which returns the same terminal
LoadStanding the old two-hop route did -- so every document's acceptance,
standing and remedy is unchanged and only the intermediate identity moved,
which is the whole claim this cut is allowed to make.

Beside those, the entry-shape note moved with the code still said this codec
emits v3. It was wrong twice: the tag moved, and the table does not own any
embedding document's tag. It now states the three disjoint entry shapes and why
an unchanged node entry survived three separate tag moves without any of them
being optional.

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

* Give every consumer of a kind-specific lookup its manifest arm

The third object kind added a wrong-kind arm to CheckoutRefusal, to the
repository mint, to the logged-commit standing and to the role-requirement
target check. A match that omits one is not a style defect: the compiler
refuses it, and the eight modules below were refusing to resolve.

The renderer gets a line of its own rather than sharing the source-file one.
The two predicaments are identically unsatisfiable, but "root is a corpus
manifest" and "root is a source file" send a reader to different places, and
collapsing them would be the partial-observer failure this branch is made of.

The live repository fixture moves to gunbc-scm-repository-v3 and
scm-object-identity-v2, because the tags it carried name a protocol this
build no longer speaks. The frozen v1 document stays exactly as it is: it is
the evidence that an unspoken version refuses as a protocol gap rather than
as damage, and it can only carry that evidence by staying unspoken.

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

* Adjudicate the namespace deltas this branch produces, and delete the consumed ones

Sixty-two rows in three classes, each under its own label because their
reasons differ: nineteen TargetChanged sites whose spelling now resolves to
gunbc.scm.object_table_json after the codec move; thirty-eight newly authored
manifest spellings that resolved to nothing at the base; five fixtures that
now name node_target_of where they could previously write a bare ObjectId.

The thirty gunbc#10355 rows are deleted rather than carried. That PR merged,
so the wall reports every one of them CONSUMED -- already satisfied at the
base -- and the roster's own rule is that a consumed row's deletion is owed
on the roster's next touch. This is that touch.

Rows are enumerated by exact identity. A pattern over the module pair would
admit a genuine rebind that happened to land between the same two modules.

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

* The sealed closure carrier had a public mint that ran no census

well_kinded_image took an unchecked CommitClosure and returned a
WellKindedClosure, discarding the derivation's census on the way. `.dag` has
no module privacy, so sole_constructor sealed the record literal while that
function stayed freely callable -- and the failure is not theoretical: a
parent requiring a node at a locator an authored source occupies comes back
in `nodes`, the encoder finds no node position for the child, and writes
`uncontained`. An occupied wrong-kind locator becomes a fetchable absence
again, which is the exact state the carrier claims to make unwritable.

This is the bearer-token defect this module already records catching one
level down, in the function that was supposed to have fixed it. The repair is
the same repair: delete the mint. The record literal now appears once, inside
the else-branch of the census that authorizes it.

The admitted arm carries the unresolved population beside the image, so the
partial admission delegates instead of walking the closure again -- one
derivation feeding both facts rather than two that can disagree. That also
removes the second walk the old path performed: admission ran closure_census
and then well_kinded_image ran derive_semantic_closure over the same closure.
admitted_well_kinded is now a field read, so it re-seals rather than
re-judges, which is what its comment already claimed.

The new claim asserts the population and not its size: two independent walks
of one closure agree on a count far more readily than on membership.

Reported by the SCM reviewer against f600f32 as the strongest blocker of
review 5117899316.

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

* Three write-side wrong-kind gaps, a dead arm, and a manifest that was not a function

FOUR MORE FINDINGS FROM REVIEW 5117899316, all the same class: a well-formed
answer of the right type about the wrong subject.

THE KIND-BLIND POSITION INDEX IS DELETED. EncodePositions carried by_identity
over every object beside the node index, and both callers that reached for it
needed a kind it does not have. A manifest entry whose source locator was
occupied by a NODE encoded as a contained `at`, which the decoder then refused
as wrong-kind -- the writer emitting bytes its own reader rejects. And a commit
root that was a manifest classified as an authored source, reporting a corpus
to an operator as a file. There is now one index per kind and no kind-blind
one, so the wrong lookup is unwritable rather than merely wrong.

That retires a declared dissolution trigger in commit_closure_json_v2 by its
own terms: the note there named "the repository object-table codec owning its
own object vocabulary", which is what the previous commits landed.

THE CHECKED WRITER READ TWO OF THREE POPULATIONS. The census has separated
absent, source-occupied and manifest-occupied since the third object kind
landed; encode_repository_checked read the first two, so a semantic edge whose
locator holds a manifest passed and was published as `uncontained` -- a
fetchable absence for a locator no fetch can repair. Reading two of three is
the same defect as reading one of two, which the paragraph beside it already
records catching a version ago.

THE MANIFEST WAS AN ORDERED MULTIMAP, NOT A FUNCTION. The design authority
says canonical corpus path -> exact authored-source target; the implementation
hashed entries in supplied order and admitted duplicate paths, so a host
directory traversal order changed the manifest identity and one path could
name two sources. Entries now canonicalize by path before the identity is
derived, and a duplicate path refuses rather than picking a winner. The four
claims are each other's controls: order-insensitivity that was bought by
hashing a bag would fail the second, and deduplication by first-or-last would
pass the first three.

THE REPOSITORY'S CLOSURE-DOCUMENT ARM HAD NO PRODUCER. It survived on a
sentence -- "a repository document really does embed a closure document" --
that stopped being true at v3, where a repository carries its own mixed table
and a commit's root designation. Deleted, with the transcribed cause count
beside it: a coproduct already states its own cardinality.

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

* The node-only decoder was reporting failures it cannot produce

Its POSITION carrier was narrowed to SemanticNodeObjectRef, so a v4 closure
document can no longer resolve a contained reference to a source or a
manifest. Its REFUSAL carrier was not narrowed with it: NodeDecodeAcc still
held an ObjectTableDecodeRefusal, whose fifteen arms include source entries,
manifest paths, manifest sources and mixed wrong-kind targets. So the closure
witness's cause-tag helper enumerated thirteen repository causes to name the
four a node table can raise, and every consumer of a closure refusal had to
answer for repository states.

That is the same dishonest domain the closure-root arms were deleted for,
arriving one layer down: the root's impossible arms were removed while the
type underneath still carried them.

THREE TYPES, BECAUSE THERE ARE THREE SUBJECTS AND ONE IS SHARED. A node
entry's syntax -- kind tag, connective, behavior, edge labels, target
spellings -- is one schema in both tables, decoded once, failing one way:
NodeEntryDecodeRefusal. What a TABLE does with a decoded entry is not shared.
A node table can fail to resolve a contained position and can collide on an
identity, and that is all it can do; the mixed table can additionally find a
source or manifest where a child was required, and can fail every way a
manifest entry fails.

The accumulators split for the same reason the resolvers already had: a
shared EdgeAcc would carry whichever refusal type its caller wanted, which is
the upward borrowing this cut removes.

The closure's arm renames to ClosureDocNodeTableRefused, because that is what
it wraps. Its cause-tag helper is now seven node-entry arms plus two node-table
ones -- the reviewer's reason for asking, made visible in the witness.

Also witnesses the manifest duplicate-path refusal from the wire, with the
two-distinct-paths control beside it so the refusal is about the repeated path
rather than about a two-entry manifest.

Closes the last structural finding of review 5117899316.

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

* A manifest's source requirement had no census, so a wrong kind became a false absence

The per-kind position index made the CONTAINED spelling of a wrong-kind
manifest source unwritable. It did not make the UNCONTAINED spelling honest:
encode_source_target asks the source-only index, and a locator occupied by a
node or another manifest is simply missing from it, so the writer emitted
`uncontained` -- a fetchable absence for a locator that is occupied and that
no fetch can repair. The narrower index moved the defect rather than removing
it, which is the same thing this module records the earlier index split doing.

ManifestSourceCensus is the missing half. It resolves every manifest entry's
source against the store through find_authored_source_record, whose four arms
already separate found from absent from node-occupied from manifest-occupied,
and it keeps three populations for the same reason ClosureCensus does.

ABSENCE IS DELIBERATELY NOT REFUSED. A manifest naming a source this
repository does not carry is a legitimate partial corpus, with exactly the
standing an absent semantic child has. What may not happen is a wrong kind
arriving in that absent population, and the control claim is what keeps the
new refusal from degenerating into "any manifest refuses".

Also, from the same review: encode_complete_closure_document discarded the
`unresolved` population the admitted arm hands it and called
unresolved_identities to walk the closure again -- one semantic fact produced
twice, restoring on the complete writer the redundant authority the mint
repair had just removed from the partial one.

And the four append-at-end accumulators. walk_target's `visited` and `nodes`,
decode_pending_edge's edges, and manifest_entry_with's entries each copied a
growing list to preserve an order one linear reverse gets for free. `visited`
is not reversed because nothing reads its order. What remains quadratic is the
membership TEST inside walk_visited, which is a different defect with a
different retirement -- a keyed collection, not an accumulator idiom -- and
the note there now says which is which instead of stating them as one.

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

* The refusal-type split left one witness naming names that no longer exist

scm_load_standing_witness classifies a refusal from every SCM format into the
shared LoadStanding vocabulary, so it names causes by hand -- and the three it
named through the closure were the pre-split spellings. The module stopped
resolving, which the wall reported as two NewUnresolvedness bindings resolving
to nothing.

I did not see it because my local evidence was six files at the time and the
full sweep was interrupted by the edits that followed it. That is the same
narrowed-observer failure this branch keeps finding in the code, arriving in
the instrument instead: a green over a set that excludes the subject.

The stale position_of row goes with it. commit_root_standing no longer calls
that function -- the kind-blind index it read was deleted -- so the row is a
standing claim about a motion that is not happening, which is what the wall
reports STALE for.

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

* The reader admitted through uncontained what the writer refuses through contained

Changing a manifest source's reference SPELLING changed whether a wrong kind
was admitted. decode_manifest_source resolves a CONTAINED position and refuses
a node or manifest sitting there; its uncontained sibling could not, because
an uncontained reference is a digest and whether that digest is occupied is a
question about the whole decoded store, which does not exist while the entry
is being read. So the checked writer refused stores this reader accepted, over
the same document.

Per-entry would still be wrong even with a store to ask, because the occupying
object may be emitted AFTER the manifest that refers to it. The judgment is
now made against the finished table, by the census this PR already added, with
both orderings executed.

Genuine absence stays admissible on both sides, and the round-trip control
proves the manifest and its missing target survive rather than merely that
nothing refused.

LoadStanding's wrong-kind arm renames to LoadRequirementAtWrongKind. It read
LoadRequiresNodeAtWrongKind, which was accurate while only a semantic child
could be requirement-shaped; a manifest entry requires an AUTHORED SOURCE and
lands in exactly the same predicament. Keeping the node in the name would make
one name carry two contracts, and adding a second standing with identical
semantics would be the same fork from the other side.

ONE CENSUS PER SUBJECT, DERIVED ONCE. Each of the five guards in the checked
writer called its own table_* accessor, and each of those re-derives a whole
census to project one field -- five walks of the store to read five fields of
two results. The two subjects stay separate: semantic child requirements and
authored-source requirements are different propositions about different kinds.

emit_in_dependency_order was the accumulator I missed. It prepends now, with
the reverse at store_records_in_dependency_order.

THE IMAGE CLAIM WAS NOT DISCRIMINATING. build_grafted_store holds exactly the
root, so "return the root-reachable nodes" and "return every semantic node in
the supplied store" produce the same one-element answer and both satisfied it.
The fixture now carries an unreachable node and the claim excludes it from
both images.

The manifest-occupied encode refusal had an arm in the tag helper and no claim
producing it -- an exhaustive tag helper is not execution of the arm it names.
Both encode refusals now assert the refused locator, not only the class.

The absence control could lose its subject: scm_env_manifest_over returned the
original store on a construction failure, so a broken fixture became an
empty-repository control that stayed green. It reports `built` now, and every
claim reads it first.

And the authority reconciliation: encode_repository_v2 emitted the v3 tag and
is renamed; "THIS FABRICATES NO v3" and "EXACTLY v2" both said the opposite of
the code beneath them; the transcribed refusal count is gone for the same
reason the object table's was; and unexpected_member_key is imported from
gunbc.scm.json_member, which declares it, rather than re-exported through the
closure module.

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

* Make the design note describe this head instead of three earlier generations

It said the closure tag should not bump (v4 shipped, and for the opposite
reason: the language got narrower); that ClosureObject has an authored-source
arm (that arm was uninhabited and the ruling changed to nodes only); that the
manifest is an ordered list (it denotes a function); and that CorpusClosure is
already modelled with outcomes and witnesses (no such carrier exists -- what
exists is ManifestSourceCensus, which is smaller and does not pretend
otherwise).

Those are incompatible design generations, not an inaccurate sentence. A plan
that quietly agrees with whatever shipped is not evidence of anything, so the
corrections are recorded as their own section rather than edited away, and the
one warning that still stands as a rule is marked as not describing this head.

The PR body is rewritten for the same reason: it still described the ordered
manifest and said both admissions mint through well_kinded_image, which is
deleted.

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

* Retire the consumed serving-engine rows and repoint the renamed writer's row

gunbc#10439 merged, so all six of its rows report CONSUMED -- already
satisfied at the base -- and the roster's rule is that a consumed row's
deletion is owed on its next touch. This is that touch.

The stale row is my own: renaming encode_repository_v2 to encode_repository_v3
moved the declaration a binding row is keyed on, so the row named a
declaration that no longer exists. It is repointed rather than deleted,
because the binding it describes still moves -- the identity changed, not the
fact.

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

* Adjudicate both requirement subjects on the reader, and let the predicament pick the standing

Four findings from external review 5118614012, all accepted.

R1. THE SEMANTIC-CHILD ROUTE WAS STILL OPEN. The previous cut closed the
manifest-source route through the repository reader and left its sibling: an
uncontained semantic child names a locator that a file or a manifest occupies,
resolve_edge_in_object_table builds the requirement without resolving its kind
-- correctly, since during entry decoding the table is not finished -- and
nothing then adjudicated the finished population. So a program whose only child
named a manifest by digest reached RepositoryDecoded while the checked writer
refused the same store. Reading one of two SUBJECTS is the same defect as
reading one of two POPULATIONS, which is what the census was split for.

The reader now derives each census once over the finished table and runs four
guards through the WRITER'S projections: they take a census rather than a store
precisely so both directions ask one authority.

R2. THE SPELLING OF A REFERENCE WAS CHOOSING A DESTRUCTIVE PERMISSION. A
wrong-kind requirement reached LoadRequirementAtWrongKind through an uncontained
digest and LoadDocumentMalformed through a contained position -- and
standing_permission_refusal permits the standing-level supersession step for
LoadDocumentMalformed alone. The four arms that positively resolved an occupant
move; the ones with no occupant to be wrong about -- an unresolved reference, an
empty or duplicated path -- stay malformed.

R3. THE LATE-OCCUPANT WITNESS NEVER PRESENTED THAT ORDER. It changed store
insertion order, but emit_in_dependency_order follows each reference through the
kind-agnostic find_object and emits the occupant first either way, so both
fixtures produced the same bytes. Deleted. The replacement rewrites the encoded
objects array and asserts the order took effect before asserting the refusal,
with a control stating what the encoder actually emits.

R4. TWO REPEATED DERIVATIONS. The reader recomputed the manifest census per
question; the checked writer built the object table twice, once to resolve
commit roots and once inside encode_repository. encode_repository_v3 splits so
the admitted envelope is assembled over the table its admission was decided on.
And four table_* projections have no caller left now that every guard takes a
census: deleted rather than left standing as a second way to ask.

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

* Name the referring entry rather than its kind, and delete the row a rename made unmatchable

TWO CORRECTIONS, ONE FROM REVIEW AND ONE FROM THE WALL.

THE WIRE-ORDER ORACLE ASKED THE WRONG QUESTION. It asked whether the first
emitted object is a manifest. That separates a referring manifest from a
leaf-node occupant and separates NOTHING in the two-manifest specimen, where
both entries are manifests and both orders answer yes -- so that claim would
have stayed green with the reversal replaced by the identity function, which is
the false positive the pair exists to rule out. The oracle is now the expected
ENTRY, reconstructed from the fixture's own path and locator, asserted against
the same document that is then handed to decode_repository. Both orders are
asserted, so dropping the reversal fails the ORDER assertion rather than
depending on the reader.

AND THE ADMISSION ROW FOR THE RENAMED WRITER IS DELETED, NOT REPOINTED AGAIN.
The previous commit repointed it from encode_repository_v2 to
encode_repository_v3 on the reasoning that the binding still moves. It does not:
a renamed declaration is a NEW declaration, the base has no
encode_repository_v3 for a target to have changed from, and the run produces no
TargetChanged delta for it at all. The wall reported it stale on two consecutive
heads. encode_repository_checked, which kept its name, keeps its row.

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

* Close the last three witness gaps: the eighth permission cell, late semantic-child order, late locators

All three are evidence gaps rather than production defects, and all three are the
same shape: a cell asserted through its sibling rather than executed.

THE PERMISSION MATRIX IS EIGHT CELLS. Two requirement subjects, two occupying
kinds, two reference spellings. Five had an executed standing and three were
standing on family resemblance: contained manifest-source at a manifest,
uncontained child at a manifest, uncontained manifest-source at a manifest. Each
refuses with its OWN cause, so each reaches the standing through its own arm,
and a matrix that asserts five and implies three is the coverage claim this
branch keeps finding in other people's work.

THE ORDERING CLAIMS COVERED ONE SUBJECT. The reason the reader adjudicates a
finished table is that an occupant may be emitted after the entry requiring it,
and that is as true of a semantic child as of a manifest source. Here the
OCCUPANT is the reconstructable entry -- a file is {"source": text}, an empty
manifest is {"manifest": []} -- so these name it rather than the referrer, in
both orders, so dropping the reversal fails the order assertion rather than
depending on the reader.

AND THE LATE REFUSALS NOW NAME THEIR OCCUPANT. The reordered manifest-source
claims asserted the cause only; carrying an ObjectId in the type is not evidence
that the right one survives a reorder.

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

* Assert the locator on every late refusal, and narrow the absence sentence to what each subject actually does

THE LOCATOR AND THE ORDER ARE TWO DIFFERENT FACTS. The order oracle proves which
requirement was presented where; the locator proves which occupying object the
refusal reports, and neither establishes the other. Every late-order claim now
asserts both, over ONE bound decode outcome rather than decoding the same
document twice -- which also retires the two standalone locator claims the
previous commit added beside them.

AND THE READER'S ABSENCE SENTENCE WAS TOO BROAD. It said genuine absence stays
admissible "matching the writer", which is true of a manifest source and false
of a semantic child: the checked writer refuses an absent semantic child as
RepositoryEncodeUncontainedTarget. What the four guards actually answer is
narrower than either policy -- they read the OCCUPIED populations only, so an
absent locator passes them whatever its subject. The comment now states the two
per-subject policies and says which of them these guards are not making. No
behaviour changes to make the sentence true.

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

* Assert the cause and the reference on the one contained cell that had no companion

ot_a_contained_manifest_source_at_a_manifest_stands_as_a_wrong_kind_requirement
asked only for the standing and the permission denial. The other three contained
cells each have a sibling asserting their exact cause; this pairing had none, so
nothing in the module required the manifest-specific cause or the reference that
names its occupant.

Three decoders satisfied it: the right one, one reporting this manifest as a
SEMANTIC NODE -- same standing, wrong kind named -- and one carrying reference
"1", the referring entry, instead of "0", its occupant. Neither the classifier
nor the permission table reads the payload, so neither mutation goes red.

The claim now binds one decoded result and asks all three questions of it.
Asserting the cause and then calling the entries-taking helper would decode the
document a second time and leave the two answers unjoined.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <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