Repository navigation
Give the SCM object store two kinds of object, so a file has somewhere to live - #9891
Conversation
…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
|
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:
The path that actually delivers an authored object into those readers today is the decoder: 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 — 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
|
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 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 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 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 |
…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
left a comment
There was a problem hiding this comment.
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.
- 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.
- 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.
- 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.
- 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:
- Separate semantic-target claim from store-specific resolution evidence, or otherwise prove the current carrier cannot admit source-occupied locators as missing.
- Make partial admission and encoding consume the three-way node-target resolution; use kind-bearing encode positions.
- Align records_equal with the full canonical preimage and add the same-store named-order discriminator.
- Make dependency order true after late graft and prove complete encoding emits no uncontained target.
- Remove the repository-v1 names under the v2 tag and correct the manifest identity-version claim.
- Add frozen repository-v1 unsupported-protocol evidence.
- Add empty and nonempty source encoder round trips.
- 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.
|
Re review 58182 (codex/gpt-5.6-sol), on The citation is fabricated. The finding says this is "the mechanical predicate-dissolution pattern prohibited by The corpus falsifies it independently. A census of There is a real question adjacent to it, and I want to answer that one rather than the invented rule: should a The distinction is preserved everywhere it has a remedy, and this is not such a place:
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 — 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
left a comment
There was a problem hiding this comment.
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
- Separate the semantic-node target claim from store-specific resolution evidence, or otherwise make source-occupied locators impossible to admit as missing.
- Make partial admission and encoding consume the three-way resolution result; make encode positions kind-bearing.
- Align
records_equalwith the full canonical identity preimage, including named-child order, and add the same-store reversed-named-order discriminator plus a non-overquotiented positional control. - 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.
- 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.
- Add an independently frozen repository-v1 document that refuses as unsupported protocol, never malformed.
- 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.
- 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
left a comment
There was a problem hiding this comment.
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_jsonandparse_json_documentbeforedecode_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:
store_contains_node_targeterases the arm and asks only whether the locator presently holds a node.uncontained_in_recordtreats everyfalseas a fetchable unresolved identity.EncodePositionsis kind-blind: it maps anObjectIdstring to a position for everyScmObject.encode_targetemits{at: ...}whenever that locator occurs in the map.- Decode then recovers the position's
ScmObjectRefand 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
|
Review 58194 (APPROVE) noted that the bundled Worth recording why the integration was done by hand rather than with the merge button, because GitHub reported Verified locally on the merged tree, with a
One environment fact for anyone else running this lane: — sent from deep-carp-676 |
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
|
Review 58294 is correct on the fact and wrong on the rule, and I have acted on the fact. Fixed. The same deletion catches a second function the finding did not name. Both are superseded by 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 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 |
…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
left a comment
There was a problem hiding this comment.
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_documentmust 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_checkedmust 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_assembledpasses; - 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 publicSemanticNodeTargetclaims and wrong-kind is a census/refusal state;- the witness says
SemanticNodeTargethas a “resolved arm,” which was deleted; repository_envelopesays 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
|
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:
That is not what this branch does, and an earlier revision of it tried exactly that and was wrong. 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 What the model actually guarantees is narrower and is the honest statement:
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 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 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
left a comment
There was a problem hiding this comment.
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
SemanticNodeObjectRefwas 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_ofis absent, whileposition_ofis present;- checked encode reports
RepositoryCommitRootNotInStorefor 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_v2still calls the authored-source addition “a strict extension, not a format version,” despite the v3 cut and frozen-v2 refusal.object_storestill says every node-requiring position takes the sealed node brand and a file in a child edge is unwritable; canonical edges now carry publicSemanticNodeTargetpropositions and wrong-kind is a census/refusal state.repository_envelopesays 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.supersessionstill auditsloaded_closure_standingas producing onlyLoadComplete | 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
left a comment
There was a problem hiding this comment.
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_loadedanswers 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_orderis 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 takesWellKindedClosure.commit_closure_json_v2: “A missing position is UNREACHABLE” is false for an admitted partial closure. Absence is the deliberateuncontainedroute; only a missing position for a target the document claims to contain through complete dependency ordering is an ordering defect.supersession: the producer audit saysloaded_closure_standingreturns only Complete/Partial and saysrepository_envelopeclassifies nothing intoLoadStanding. Both are now false;repository_envelope_load_standingis 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.
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
left a comment
There was a problem hiding this comment.
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
- Correct the four exact source-text contradictions above in one pass.
- Correct the PR receipt table and disposition sentence.
- Obtain terminal exact-head CI after the
.dagcomment edits.
No further model or witness redesign is requested. No approval carries on 689a2b5.
…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
left a comment
There was a problem hiding this comment.
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.
…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>
… 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
…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>
Why this is a model change and not a wiring one
gunbc scm add <file>was blocked, and the blocker was the object model.ObjectStoreheldList<ObjectRecord>— aNodeKindplus labelled children — so a file's bytes had exactly one routein: intern them as an Atom's
Symbol. That makes the symbol table a content store and gives one nametwo materially different meanings (DESIGN §3's meaning fork). It is the same wrong model I already
rejected for the srv2 mirror, so building
addon it would have cemented it.What lands
ScmObject = SemanticNodeObject(ObjectRecord) | AuthoredSourceObject(AuthoredSourceRecord), underone content identity family — a store holding two identity kinds would make
store_containsambiguous about what it contains. The authored identity is derived from the bytes by
content_hash_atomunder a family tag, so a caller supplies content and never an identity: the samewall
store_nodeandinsert_from_structurealready stand behind, reached by a third producer ratherthan a second identity authority.
AuthoredSourceContent = AuthoredSourceEmpty | AuthoredSourceText(NonEmptyStr).content_hash_atomtakes a
NonEmptyStr, so the degenerate empty file is a constructor rather than a checked specialcase.
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 | Unresolvedsum, on the reasoning that apartial closure must reference an object it does not carry. The premise was right and the carrier was
wrong: the hash ignored the arm,
records_equalhad 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_targetansweredBool, andfalsemeant 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.
ClosureCensusnow separatesabsentfromsource_occupiedin one traversal and keeps them apartall the way up:
unresolved_identitiesprojectsabsentonly,closure_is_completerequires bothempty, and admission grew
ClosureRequiresNodeAtAuthoredSourcenaming 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_constructorwall — and a partial closuremust 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:
against a node-only map, so there is no entry to find.
encode_closure_documenttakes aWellKindedClosure—
sole_constructor, mintable only throughadmit_well_kinded_closure, which runs the census.Guarding the entry points was tried and was not enough:
.daghas no module privacy, so the rawencoder 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.
all.
WellKindedClosureissole_constructor, so there is no refusal to execute: the state has noconstructor at the call site. This is the rung-4 arm and the only one that is.
closure_is_completereturns a Bool. It reports the closure as not complete;it does not name why, and it is not a refusal.
ClosureRequiresNodeAtAuthoredSource), the completewriter (
ClosureDocRequiresNodeAtAuthoredSource),encode_repository_checked(
RepositoryEncodeTargetIsAuthoredSource), the commit-root boundary(
RepositoryEncodeCommitRootIsAuthoredSource), andloaded_closure_standing(
LoadRequiresNodeAtAuthoredSource, for whichstanding_loadedanswers false — the document readfine, 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-v3and the repository tag isgunbc-scm-repository-v2; the module's own stated policy is that a schema which grows a member growsa new format tag. The repository dispatch arm and codec functions are named
…V2/…_v2to matchthe tag they implement — they read
v1under a v2 tag, which was a meaning fork in the names.commit_closure_json_v2.dagstill carries a_v2suffix while emitting the v3 tag. The tag is thecontract; the rename is deferred with a stated
dissolve-onbecause it costs 41NAMESPACE_TRANSITION_ADMISSIONSrows 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:
a_node_requirement_over_a_file_is_not_a_fetchable_absencere_writing_the_same_bindings_in_another_order_is_idempotentscm_rl_a_frozen_v1_document_is_a_protocol_gap_not_damagea_complete_closure_spells_its_child_containeda_completed_parent_first_graft_has_the_naturally_built_imagescm_image_a_requirement_over_a_carried_file_is_not_partialEach 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_nodecanonicalizes before persisting;insert_from_structurederived the locator from canonicalized children and then stored them assupplied. Writing
[alpha, beta]then[beta, alpha]derives one locator, so the second writereported a
LocatorCollision— a fabricated refusal for an ordinary write.store_records_in_dependency_orderreturned insertion order on the argumentthat
store_nodemaintains it. True forstore_node, false for the graft route:copy_objectappends 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 == 34escape takes both red.One arm ships without a discriminating RED and is declared rather than hidden:
RepositoryEncodeCommitRootIsAuthoredSource. Reaching it needs a store where aSemanticNodeObjectRef'slocator 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
addandcommitverbs — they needCorpusManifestObjectand a staging authority, not justScmWriteOutcome. And merge remains model-blocked:gunbc.scm.mergeis roles/requirements/supersession, not two-commit merging with a derived common ancestor. That needs a design first.