Skip to content

Emission follows resolution: remove the std::string::String ↔ Rc<im::Vector<i64>> expected/found family against the 155-row boundary - #9850

Merged
briansrls merged 2 commits into
mainfrom
session/bold-carp-449
Sep 1, 2026
Merged

briansrls merged 2 commits into
mainfrom
session/bold-carp-449

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Claim (mechanism grain)

This PR removes the exact std::string::String ↔ Rc<im::Vector<i64>> expected/found E0308 family against the 155-row receipt-1 boundary (main@eced9d24c599, tree d424154bb6fb, census digest 049d0a54eea70534), with every successor row classified. It does NOT claim "all String bugs repaired" and predicts no remainder — the next census classifies additions, it never subtracts.

Nat-family occupancy is ZERO in the live census; the nine LLVM IR identities are String-family. The retired ≤11 bound stays retired.

Root cause (authority_substitution at the env)

rewire_type_env_import_str_binding_identity (04_infer.dag) runs post-fold over the run's assembled TypedModules and, for any inherited name with exactly one exporter in the invocation's module list (build_export_indexes — run-scoped, not corpus-scoped, not intrinsically closure-scoped) and ≤1 direct-import exporter, REPLACES the ancestry binding with that exporter's declaration. String has one closure exporter (v2.std.text; std.string_type is outside the 00_compile closure), so every persisted env lost its kernel <kernel:String> binding. Inference ran pre-rewire (kernel answer, host String bodies); emit read post-rewire (structural answer) → the 139-row pub use crate::v2_std_text::{String} shadow.

Two natural-experiment confirmations, pre-registered: (1) v2.std.compilers.target_model imports v2.std.text via TWO import statements → direct-exporter count 2 → skip arm → only module where the consumer filter fired pre-producer-fix; (2) Int has two closure exporters (std.integer + v2.std.integer) → skip arm → kernel binding survived corpus-wide. The drops occurred precisely on the skip-arm's complement.

The fix (two halves, both identity-keyed)

  1. Producer (ancestry_binding_is_kernel_identity, 04_infer.dag): the rewire skips any ancestry binding whose resolved ident_span.file == "<kernel:NAME>" — keyed on the binding's own identity, never a name roster.
  2. Consumer (import_name_resolves_to_kernel, 05_emit_rust.dag): use-line derivation consults the resolver's own walk (lookup_type_by_name over the module's TypeEnv) and drops a name only when it resolves to a structureless kernel scalar (<kernel: file, NoConnective, 0 children). Structural kernels (Bool, Optional) and non-kernel bindings (Witness) keep their lines. Absent binding/env → keep: ignorance never widens a drop.

Exact closing decomposition (155 → 66, identity grain, remainder EMPTY)

Measured at the byte fixed point (regen round 1, first_generation_equal=true; unit tests 612 passed / 0 failed).

66 = 14 baseline survivors + 52 classified additions. The set of rows that is neither a predicted survivor nor a classified addition is EMPTY.

Survivors (14):

  • 9 at identical positions: gunbc_v1_interpreter_primitive_surface.rs:254 E0599 concat; v2_compiler_infer.rs:1323 E0308; v2_compiler_infer.rs:489 + v2_compiler_parse.rs:529 E0282; v2_std_collection.rs:93 E0308 + :94 E0560; v2_std_text.rs:64/72/90 E0599 chars.
  • 5 shifted by exactly −1 line (one use-line dropped per file), matched to their baseline row by file+code+label: target_model.rs:2606 (String↔i64), target_model.rs:7224 (Ownership lookup pair), target_carriers.rs:105 (Option<Rc<Node>>), compile.rs:288 (Witness nesting), source_authority.rs:1007 (Rc<Node>↔Rc<NormalizedTree>).

Additions (52), all classified, exact counts:

  • 48 E0308 String ↔ Rc<Vector<i64>>/Rc<Vector<_>> body-boundary rows (bodies binding kernel host String while calling structural v2.std.text/algebra fns; both mismatch directions; 4 of the 48 render as "arguments to this function are incorrect" whose expected/found notes carry the same pair, and 5 spans carry two diagnostics each — which is why 66 rows sit over 61 distinct span+code keys).
  • 1 E0282 at v2_compiler_tokenize.rs:151:77 — inference failure induced by the same boundary (a closure over is_prefix_of(prefix, source, …) where String is now host).
  • 3 at target_model.rs:739 — the pre-registered known rows: E0277 str-indexing, E0599 iter on str, E0308.

Removals (141 = 155 − 14): the pre-registered 139 String-family E0308, plus 2 rows predicted to survive that were also removed — v2_std_qualified_name.rs:238:68 E0599 (split on Rc<im::Vector<i64>>) and v2_std_compilers_lexing.rs:182:72 E0282 — both String-representation rows the prediction had misfiled as heterogeneous survivors. Reported as divergence, not reconciled: the predicted survivor set was 16; the measured set is 14.

Pre-registration scoring

  • Predicted use-line drops — CONFIRMED: v2_std_text::{String} 18 → 0; v2_std_integer::{Int} 9 → 0.
  • Predicted skips — CONFIRMED: Bool ×32, List ×46, Map ×16, Witness ×22 use-lines all kept.
  • Predicted removals — CONFIRMED plus the 2 favorable-divergence rows above.
  • Predicted additions class — CONFIRMED with zero unclassifiable rows; the volume (49 String-boundary rows vs 5 known candidates) is a divergence reported as measured.
  • No falsifier fired: no String line survived, no Bool/Witness/List line dropped, no predicted-removed identity persisted.

Finding — additions concentration: 38 of the 48 String-boundary E0308 additions sit in v2_compiler_tokenize.rs (86% of the class in one module once the 4 arguments-form rows are included: 42/49). Kernel-host String bodies calling structural text/algebra fns cluster where the tokenizer folds lexemes; the next lane on the String body boundary should start there.

Method note: the discipline used here — pre-register predicted sets and falsifiers before measuring, score at identity grain, report every divergence instead of reconciling it — caught two authoring errors before measurement (the "fired in only one module" misread; the corpus-vs-run scope of build_export_indexes) and, post-measurement, caught the survivor-count misfiling above. One addition from the score itself: score BOTH directions — the prediction can be wrong about what stays as easily as about what goes; the miss here was a predicted SURVIVOR removed, which a removals-only scoreboard would have scored 139/139 and called clean. The next receipt inherits this as method.

Finding: run-scoped resolution divergence (narrowed, not closed)

build_export_indexes is scoped to the invocation's assembled module list, so a scoped run and a whole-corpus run could bind the same name to different declarations. This fix removes that run-dependence for kernel names only — the divergence is narrowed, not closed. The explicit scope field on CompilePhaseReceiptSubject lands with receipt 2, not here (carried by the manager as a known gap).

🤖 Generated with Claude Code

https://claude.ai/code/session_01GXfYKNQTD3VfYyQcnJpxNU

gunbc-ci-auto-heal and others added 2 commits August 31, 2026 23:30
…key use-line derivation on the resolved kernel identity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXfYKNQTD3VfYyQcnJpxNU
…ion_equal=true)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXfYKNQTD3VfYyQcnJpxNU
@briansrls
briansrls merged commit 206e00e into main Sep 1, 2026
12 checks passed
@briansrls
briansrls deleted the session/bold-carp-449 branch September 1, 2026 01:39
@briansrls
briansrls restored the session/bold-carp-449 branch September 1, 2026 01:40
gunbai-bot Bot pushed a commit that referenced this pull request Sep 1, 2026
…icted generated mirrors taken from main pending regeneration from the merged authority

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4A6MRdzeYxNr7dRbugcUC
briansrls pushed a commit that referenced this pull request Sep 1, 2026
Resolves six unmerged paths from the squash-merge of #9850.

Two were genuine .dag authority conflicts, resolved toward this branch's
newer content. One mattered beyond recency: main's side of 05_emit_rust.dag
still carried string_contains(sp.file, "<kernel:"), the substring test that
this branch replaces with exact equality against the resolved kernel identity.
Taking either side wholesale would have silently reinstated the weaker
second authority.

Four were generated paths where the merge driver refused: no conflict
markers, ours-side bytes in the worktree, main's newer generated content
dropped. A marker grep reads that tree as clean; git ls-files -u does not.
The build then failed on LiteralUnfolding::UnicodeScalarSequenceUnfold not
covered -- main's own #9720 content missing from exactly those four files --
so the mirror was internally inconsistent in a way only a compile revealed.
Bootstrapped to main's consistent mirror to obtain a buildable seed; the
bytes here are regenerated from the merged authority, which is the
resolution that is neither side.

#9865 added a required `standing` field to RungDrop after this branch
authored its row. The text merge combined both sides with no conflict and
the typecheck refused with an exact location, which is the fail-closed
property working as intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDnHQs9oE5AKJXe1vReU8v
gunbai-bot Bot pushed a commit that referenced this pull request Sep 1, 2026
…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
gunbai-bot Bot added a commit that referenced this pull request Sep 2, 2026
…e to live (#9891)

* Give the SCM object store two kinds of object, so a file has somewhere 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

* Apply the authored-source tagging rule to both arms, and make the invariant 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

* Execute the authored-source checkout refusal instead of arguing it from 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

* Refuse for the right owner: split MemberFailed, and stop reporting a 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

* Put every annotation above a subject, and ask the store for a NODE where 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

* Brand a node reference so a file cannot inhabit a program position, and 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

* Compare records by what the identity is derived from, and count the durable 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

* Name the identity rule for its subject, and freeze a predecessor document 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

* The module names the codec and the tag names the version: retire a suffix 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

* Restore the rung-drop row an auto-merge deleted, and revert a module 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

* Execute the authored-source wire round trip in both arms

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

* Make a node requirement a proposition, and let only the store answer it

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

* Canonicalize on the other route too, and stop fabricating a collision

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

* Derive dependency order instead of trusting insertion order

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

* Delete the two node-presence predicates the census replaced

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

* Name the repository codec for the tag it implements, and freeze a real 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

* Read both census populations everywhere, not just the absent one

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

* Seal the raw closure encoder behind a carrier the bad state cannot inhabit

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

* Separate a wrong-kind commit root from an absent one, and execute the 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

* Make the commit-root fold exhaustive instead of rostering its wildcard

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

* Carry the standing to its destination, scope the image claim, sweep the 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

* State the populations that exist, not the ones these notes were written 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

* Exclude witness helpers from the census denominator, by name

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

* Regenerate the Rust source-type bindings from their authority

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

* Two claims quantified over a population they did not check

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

---------

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