Skip to content

v4 B1-CANON: design the canonical-form clause in-header (not delegated) - #3157

Merged
briansrls merged 5 commits into
mainfrom
v4-canonical-form
May 16, 2026
Merged

briansrls merged 5 commits into
mainfrom
v4-canonical-form

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Summary

Operator-ratified 2026-05-15. The canonical-form clause — the total deterministic normal form B1's content_hash folds over — was previously deferred ("T-1 worker encodes per relay"). It is the single most load-bearing definition in the substrate (B1, A3, T-15, T-20, T-21, and AGENT-1's content-addressed Diff all rest on it). Pulled back from delegation and designed + ratified as the B1-CANON block in std/node.dag's header. T-1 implements content_hash = merkle_fold ∘ canonical to this contract; it does not redesign it.

The 6-point contract

  1. Domain — post-normalize (C3-desugared) kernel only; sugar variance can't reach the hash.
  2. α-equivalence (K-1) — bound occurrences → binder-relative De Bruijn position; free Symbols keep opaque identity. Rename-a-local ⇒ same hash (affected_set Ex.2).
  3. Child order is ALGEBRA-DERIVED, never hand-flagged — derived from connective algebra inhabitance (std/algebra.dag). Default SEQUENCED; commutative only where algebra proves it. Asymmetry is deliberate + fail-closed: sequenced-as-commutative = silent correctness bug; commutative-as-sequenced = mere over-fire.
  4. Recursion by-reference — name-ref Atoms hashed by Symbol identity, never inlined ⇒ content_hash total on type T = … T ….
  5. Totality/decidability — structural catamorphism over a finite Node tree; Merkle ⇒ shared sub-Nodes hash once (content-addressing/dedup/affected_set unchanged-subgraph detection all free).
  6. No incidental ordering — only algebra-proven sequenced child order survives.

Changes

  • std/node.dag — B1-CANON header block + Owns entry
  • DECISIONS.md — B1 row + Part 3 note updated (designed-here, not delegated)

Header design contract only (T-1 implements). Separate authority from A1 (per ratified B1). No code change — v2 bridge parses clean.

Test plan

  • cargo fmt --all --check clean
  • v2-compiler compile --source-root src/v4 --target dag → 0 diagnostics
  • reviewer pass (esp. point 3 — the algebra-derived child-order claim is the highest-risk)

🤖 Generated with Claude Code

Operator-ratified 2026-05-15: the canonical-form clause was deferred to a
T-1 worker relay. Every consumer (B1, A3, T-15, T-20, T-21, AGENT-1's
content-addressed Diff) rests on it — too load-bearing to delegate. Now
designed + ratified as the B1-CANON block in std/node.dag's header; T-1
IMPLEMENTS content_hash = merkle_fold ∘ canonical to it, does not redesign.

6-point contract: (1) post-normalize domain; (2) α-equivalence via K-1
(De Bruijn for bound, opaque identity for free); (3) child order is
ALGEBRA-DERIVED not hand-flagged — default SEQUENCED, commutative only
where algebra proves it (fail-closed: over-fire safe, collision = silent
correctness bug); (4) recursion by-reference, name-ref hashed by Symbol
identity never inlined (totality on `type T = … T …`); (5) structural
catamorphism, decidable, Merkle ⇒ shared sub-Nodes hash once (dedup
free); (6) no incidental ordering. DECISIONS.md B1 row + Part 3 updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request May 15, 2026
…ntracts

Derisk (operator-requested). The Ex.1-4 worked examples proved correctness;
these prove the MINIMALITY claim is falsifiable and bind its real gates:

- Ex.R1 (effectful local edit): minimal IFF the effect algebra is
  fine-grained — an effect-GRANULARITY property, not effect-lens strength
  (B3 makes effects structural/read, so the lens is strong by
  construction; coarse effects are the open risk). Contract on
  lens/effect.dag.
- Ex.R2 (widely-shared sub-node, partial change): minimal IFF infer
  emits NODE-PRECISE use→def edges to the consumed sub-Node. The dominant
  gate — more load-bearing than the effect lens. Contract on
  compiler/04_infer.dag (T-9).
- Ex.R3: "what counts as changed" is retired-safe by B1-CANON (#3157) —
  fail-closed sequenced default ⇒ worst case over-fire, never a missed
  change. No residual research risk on Diff granularity.

Consumes section sharpened with the two implied contracts + the B1-CANON
Merkle dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review metadata

  • Provider / model: codex / unknown
  • Commit: 79cc7076 · Trigger: schedule
  • Thinking: 141s wall

BLOCKING (1)

Root Cause

  • src/v4/std/node.dag B1-CANON combines equality-only Symbols with a sort-by-child-hash normalizer → define canonicalization over labeled child entries with a declared order-independent fold or structural tie/collision policy before T-1 implements content_hash.

Non-blocking — Strengths

  • src/v4/DECISIONS.md The ledger correctly pulls the canonical-form clause back into the substrate authority instead of leaving this load-bearing B1 contract to worker discretion.

⚠️ One blocking canonicalization determinism gap should be fixed before this substrate contract becomes the implementation target.

Comment thread src/v4/std/node.dag Outdated
// 3. CHILD ORDER IS ALGEBRA-DERIVED, never hand-flagged (the subtle,
// highest-risk point). Whether a node's children are SEQUENCED
// (order is content) or COMMUTATIVE (order quotiented — canonical
// sorts those children by their own recursive content_hash) is

This comment was marked as resolved.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: 79cc7076 · Trigger: manual
  • Comparison: main @ b8e91b6d ... v4-canonical-form @ 79cc7076
  • Conversation: View conversation

1. Story of the diff

This PR moves the canonical-form decision for v4 Node hashing out of worker discretion and into the std/node.dag authority itself. src/v4/DECISIONS.md:43 and src/v4/DECISIONS.md:105-111 reframe B1 as “B1-CANON”: T-1 is no longer responsible for designing canonical form, only for implementing content_hash = merkle_fold ∘ canonical. The new src/v4/std/node.dag:90-149 header then defines the six-point canonical-form contract: post-normalize input domain, α-equivalence through K-1/De Bruijn handling, algebra-derived child ordering, recursive references by symbol identity rather than inlining, structural totality/decidability, and exclusion of incidental order.

2. Invariant categories

  1. LAYER MODEL — Finding, BLOCKING. This diff touches substrate authority: src/v4/std/node.dag:90 introduces “B1-CANON … the canonical-form clause,” and src/v4/std/node.dag:98-99 makes content_hash = merkle_fold ∘ canonical the implementation target. The load-bearing gap is in the commutative child-order rule: src/v4/std/node.dag:112-113 says canonical form “sorts those children by their own recursive content_hash,” but the contract never specifies what happens when two distinct canonical children have the same hash. Because src/v4/std/node.dag:136-140 also claims canonical is a total decidable catamorphism, hash-only ordering needs either a structural tie-breaker, a typed collision failure, or an explicit injective-hash model. Without that, the canonical Node -> Node normal form can remain source-order-dependent or silently collapse distinct children under a digest collision.
  2. INVARIANTS.md + modeling-discipline.md — Finding, BLOCKING. This is the same substrate issue viewed through P3 Fail-Closed and P2 Boundary Discipline / single authority. src/v4/std/node.dag:118-122 correctly identifies that collapsing distinct sequenced programs into one hash is a “silent correctness bug,” but the hash-sort rule at src/v4/std/node.dag:112-113 still lets the digest become the authority for child ordering without a collision outcome. A collision must not be treated as impossible by convention in the canonical authority; the design should fail closed or sort by a collision-safe structural canonical representation.
  3. CODING.md — N/A. The diff is design/header text only; it adds no Rust functions, methods, error shapes, helpers, or implementation APIs to evaluate against Rust coding style.
  4. TESTING.md — N/A. No executable implementation changes in this PR. The test obligation belongs to the downstream T-1 implementation of canonical / content_hash, where behavior-driven cases should cover α-renaming, algebra-derived commutative ordering, source-order irrelevance, recursive references, and the collision/tie case above.
  5. LOCKED DESIGN DECISIONS — Compliant. The PR makes the locked decision explicit rather than diluting it: src/v4/DECISIONS.md:122-135 states that B1-CANON is separate from A1 and fixed as a six-point contract, while src/v4/std/node.dag:90-99 puts that authority in the node header and limits T-1 to implementation.
  6. TRACKED vs UNTRACKED DEBT — Compliant. No new TODO, scaffold, or temporary bridge is introduced. The handoff is bounded: src/v4/std/node.dag:97-99 says T-1 implements the already-ratified canonical / content_hash contract rather than redesigning it.

2.5. Top-down PM intent review

Compliant. The PR preserves the high-level intent: it strengthens the single-authority substrate direction by locating the canonical-form contract in std/node.dag and preventing a worker from inventing a parallel hashing definition later. The blocking issue above is a precision gap inside the canonical contract, not semantic dilution of the PM-level plan.

3. Verdict

REQUEST_CHANGES. The PR is directionally right and mostly clean, but B1-CANON is substrate authority for every content-addressed consumer, so the hash-only sort rule must specify collision/tie behavior before landing. A one-sentence fix in the contract may be enough if it makes the normal form total and fail-closed.

briansrls added a commit that referenced this pull request May 15, 2026
…ANGES)

Both findings valid; fixed structurally rather than patched.

Finding 1 (node.dag:~478 ByPosition{index:Int} admits negatives): drop
EdgeSelector/ByPosition entirely. Path = List<Symbol> — each step
descends the Named edge keyed by that opaque Symbol. No ordinal exists,
so the negative/out-of-range illegal state is structurally impossible
(dissolved, not deferred to path resolution). Positional-connective
children are reached by replacing the enclosing Named subtree (the safe
over-fire #3157 blesses); surgical positional addressing is a future
operator-ratified extension (consumes-nothing root has no Nat; recursive
Peano = A1 STOP).

Finding 2 (node.dag:~514 Diff=List<Edit> validity via external
predicate): drop the set / pairwise-independence / parallel-positions
framing and paths_independent/diff_well_formed. Diff = ORDERED List<Edit>
applied SEQUENTIALLY; any List<Edit> is a valid rewrite program (no
representable illegal state). A non-resolving Edit => fail-closed
Diagnostic at apply time (operational, not a type-level illegal state);
apply_diff is a fold, all-or-nothing.

Both refine the operator-ratified shape — flagged in DECISIONS.md D1
PROPOSED-for-operator-eye for confirm/redirect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request May 15, 2026
B-5 (affected_set R1 usefulness): effect is a per-Arrow-Node fact from
the signature (B3), never aggregated coarse — a granularity invariant
(coarse ⇒ R1 correct-but-useless). effect.dag B-5 block + affected_set
Consumes/Ex.R1 cross-ref + DECISIONS B-5 row.

Subsume #3153 (v4-affected-set-design, no worker — operator-directed):
its 4 worked-examples + adversarial R1/R2/R3 + dogfood-reality folded
into affected_set.dag as the T-21 design contract, reconciled to the
now-ENCODED reciprocal contracts — Ex.R1→B-5 (effect.dag), Ex.R2→B-4
(04_infer.dag), Ex.R3→B-3 (drafted #3157 point-3 correction). #3153
superseded; recommend close. DECISIONS #3153-subsumed row.

B-2 (PROOF-1 prover model lands): extdeps/languages/lean.dag (new) —
Lean first, termination theorem class; Coq deferred second-prover
probe. B2-OMNI requires the LanguageModel file PROOF-1 #3158 deferred.
STRUCTURE enumeration+count 63→64; DECISIONS L-4; TASKS PROOF-1 note.

Includes the already-committed origin/main merge (204b855: #3158
PROOF-1 + #3160 meta-cut; DECISIONS.md conflict resolved keeping
D1/B-4/PROOF-1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B-3 (operator-greenlit): strike "child order is ALGEBRA-DERIVED".
The commutative-quotient is structurally foreclosed two independent
ways — (a) no sort key: Hash is opaque/equality-only (K-1/B1), "sort
by content_hash" inexpressible; (b) algebra unreachable: canonical/
content_hash are pure folds in the consumes-nothing root, std/algebra
.dag Consumes node.dag (reading back = the D1 cycle class) and
inhabitance is bound by infer (T-9), downstream of canonical's
pre-infer domain. So child order = `children`-list order
unconditionally — the correct closure of a decidable system
(INVARIANTS:31), not a defect. Only field-permutation dedup is lost
(over-fire = safe; missed-change foreclosed). T-2/algebra.dag needs no
change — point 3 was mislayered, not deficient. Point 3 rewritten,
point 6 de-qualified, DECISIONS B1-CANON prose updated.

Also resolves the origin/main merge: DECISIONS B1 row merged (keeps
both #3157's B1-CANON-block-is-operator-designed framing and main's
Hash-re-homed-to-node.dag + structural-caching-consumer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Valid (P2/audit-trail) — fixed in the latest commit. The dates were internally correct (the B1-CANON clause is operator-ratified 2026-05-15 at node.dag:92 / DECISIONS:43; the B-3 point-3 amendment is 2026-05-16) — but node.dag:113 phrased B-3 as "operator-ratified 2026-05-16" without stating it amends the 2026-05-15 clause, so it read as two timestamps for one ratification.

Reframed both sites to make the relationship explicit: "B-3 — an amendment to the 2026-05-15 B1-CANON clause, not a separate ratification; B-3 itself operator-ratified 2026-05-16" (node.dag point-3 + the DECISIONS B1-CANON prose). One audit trail, no apparent conflict; the substance is unchanged.

— sent from valiant-boar-161

…NTS)

Valid P2/audit-trail finding. The dates were internally correct
(B1-CANON clause = 2026-05-15; B-3 point-3 amendment = 2026-05-16) but
node.dag:113 phrased B-3 as "operator-ratified 2026-05-16" without
saying it AMENDS the 2026-05-15 clause — reading as two conflicting
timestamps for one ratification. Reframed both node.dag point-3 and
the DECISIONS B1-CANON prose: B-3 is explicitly an amendment
(2026-05-16) to the 2026-05-15 B1-CANON clause, NOT a separate
ratification. Single audit trail, no apparent conflict.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review metadata

  • Provider / model: codex / unknown
  • Commit: a85b22cb · Trigger: schedule
  • Thinking: 184s wall

BLOCKING (1)

Root Cause

  • src/v4/std/node.dag The canonical-form contract assigns bound-reference structure that the Node atom carrier cannot encode → either add a substrate-representable bound-reference shape or restate content_hash as an environmented fold that does not claim to materialize canonical : Node -> Node.

Non-blocking — Strengths

  • src/v4/DECISIONS.md The ledger now makes B1-CANON the substrate authority and keeps T-1 in implementation mode rather than delegating a load-bearing design decision.

⚠️ One substrate-level representability gap remains before this canonical-form contract is safe to hand to T-1.

Comment thread src/v4/std/node.dag Outdated
// normalized kernel (the 4 sugar forms already dissolved). Raw
// parse trees are out of domain — sugar variance cannot reach the
// hash because it is gone before canonicalization.
// 2. α-EQUIVALENCE (K-1). A BOUND occurrence is replaced by its

This comment was marked as resolved.

…3157 BLOCKING)

Verified against current code: B-3 (64bf5a7) already REMOVED the
commutative-sort-by-content_hash; child order is unconditionally
`children`-list order, so the finding's premise ("B1-CANON sorts
commutative children by content_hash") describes pre-B-3 behavior and
its nondeterminism concern is already foreclosed (no sort exists).

But the reviewer's argument is a genuine THIRD independent reason the
old commutative-quotient was unsound, so it is folded into point-3's
foreclosure rationale as reason (c): distinct labeled children can
share an identical child subtree (`{a:Int,b:Int}` ⇒ equal child
content_hash) and K-1 gives the label Symbol equality-only (no order)
⇒ no deterministic tie-break ⇒ incidental-order fallback (P2/P4).
Strengthens B-3 (not contradicts); the BLOCKING is incorporated, not
stale-dismissed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Verified against current code (not stale-dismissed) — and incorporated.

The finding's premise ("B1-CANON sorts commutative children only by child content_hash") describes the pre-B-3 behavior. B-3 (commit 64bf5a782) already removed the commutative-sort entirely: current node.dag point-3 is "CHILD ORDER IS children-LIST ORDER, UNCONDITIONALLY … the COMMUTATIVE/quotient branch is STRUCTURALLY FORECLOSED." There is no sort, so the nondeterminism / incidental-order-fallback this BLOCKING describes cannot arise (list order is total ⇒ deterministic by construction). So the conclusion is already addressed.

But your hash-collision argument is a genuine third independent reason the old commutative-quotient was unsound — sharper than B-3's reason (a) ("no Hash order"). I've folded it into point-3 as reason (c): distinct labeled children can share an identical child subtree ({ a: Int, b: Int } ⇒ equal child content_hash), and K-1 makes the label Symbol equality-only (no order) ⇒ no deterministic tie-break ⇒ incidental-order fallback (INVARIANTS P2/P4). It now stands alongside (a) no Hash order and (b) algebra unreachable as a third foreclosure reason — so the BLOCKING is concretely incorporated into the audit trail, not argued away.

— sent from valiant-boar-161

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: a85b22cb · Trigger: manual
  • Comparison: main @ 63e4a634 ... v4-canonical-form @ a85b22cb
  • Conversation: View conversation

1. Story of the diff

This PR turns B1’s canonical-form clause from something a T-1 worker would “encode per relay” into a substrate-level, in-header design authority. src/v4/DECISIONS.md:43 re-homes the B1 description around the std/node.dag B1-CANON block and makes content_hash = merkle_fold ∘ canonical an implementation contract, not a delegated design choice. src/v4/std/node.dag:92-101 then carries the load-bearing authority directly: B1-CANON is separate from A1, every B1/A3/T-15/T-20/T-21/AGENT-1 consumer depends on it, and T-1 only implements the contract.

The actual canonical contract is six-point and deterministic: canonicalization is post-normalize only (src/v4/std/node.dag:103-106), bound symbols canonicalize by De Bruijn position while free symbols retain opaque K-1 identity (src/v4/std/node.dag:107-111), child order is always children list order (src/v4/std/node.dag:112-145), recursion hashes by reference instead of expansion (src/v4/std/node.dag:146-153), hashing is a finite structural catamorphism/Merkle fold (src/v4/std/node.dag:154-162), and incidental source/import/binding/whitespace ordering is excluded (src/v4/std/node.dag:163-168). Net effect: this PR designs the identity boundary for future hashing/affected-set consumers; it does not implement the hasher yet.

2. Invariant categories

  1. LAYER MODEL (substrate vs implementation).

Compliant — this touches substrate authority, but in the expected place: std/node.dag owns the Node canonical-form contract and explicitly separates B1-CANON from A1 (src/v4/std/node.dag:92-95). No new substrate connective, behavior, Dag field, or parallel runtime representation is introduced; the change is an in-header contract for a future implementation.

  1. INVARIANTS.md + modeling-discipline.md.

Compliant — boundary discipline / single authority is handled by making B1-CANON “Designed + ratified HERE, not a worker-relay choice” (src/v4/std/node.dag:96-99) and by having DECISIONS point to that same authority (src/v4/DECISIONS.md:43). Fail-closed / decidability are handled by making raw parse trees out of domain (src/v4/std/node.dag:103-106), recursion by-reference rather than inlined (src/v4/std/node.dag:146-153), and the fold finite/decidable (src/v4/std/node.dag:154-162). This matches the reference rubric’s single-authority, fail-closed, decidability, and dissolution principles. chatgpt-review-108e3e63-8da0-4e…

  1. CODING.md.

N/A — no Rust implementation, functions, methods, error/result shapes, globals, or helper placement changed. The eventual T-1 implementation should follow the project’s data + pure-function style, but this PR only names the pure contract (canonical : Node -> Node; content_hash = merkle_fold ∘ canonical) at src/v4/std/node.dag:99-101; there is no code surface to style-review. chatgpt-review-65a7daba-e506-4f…

  1. TESTING.md.

N/A — no executable canonicalization or hash implementation is added, so there is no behavior to test in this PR. The test obligation belongs to the later T-1 implementation of content_hash = merkle_fold ∘ canonical (src/v4/std/node.dag:99-101, src/v4/std/node.dag:154-162), ideally as .dag TestClaim-shaped coverage when that surface exists. chatgpt-review-0309c50a-bc47-4e…

  1. LOCKED DESIGN DECISIONS.

Compliant — the diff records a locked/ractified design rather than silently diverging from one. It explicitly says B1-CANON is separate from A1 (src/v4/std/node.dag:92-95), ratified in-header rather than delegated (src/v4/std/node.dag:96-99), and that T-1 implements but does not redesign it (src/v4/std/node.dag:99-101). The B-3 amendment is also scoped as an amendment to the B1-CANON clause, not a separate competing ratification (src/v4/std/node.dag:112-115).

  1. TRACKED vs UNTRACKED DEBT.

Compliant — I do not see a new scaffold, TODO, bridge, or temporary representation. The only staged boundary is explicitly bounded: T-1 is named as the implementer of the already-fixed contract, not a co-authoring bridge (src/v4/DECISIONS.md:106-112, src/v4/std/node.dag:99-101). No untracked debt is introduced.

2.5. Top-down PM intent review

Compliant — the highest-level v4 authority says the active program is v4 and thesis claims apply to the v4 substrate, while the thesis also frames correctness as structural facts carried by the program model rather than hand-maintained behavior. chatgpt-review-6a3c5bc6-9dce-48…

chatgpt-review-6a3c5bc6-9dce-48…

This PR preserves that intent: it moves a load-bearing canonical identity decision into std/node.dag itself (src/v4/std/node.dag:92-101) and avoids adding hand-written implementation or worker-local policy. I do not see semantic dilution: the must-have target does not become optional, no removal/dissolution goal becomes permanent scaffolding, and no handwritten Rust authority is added against the zero-floor direction. chatgpt-review-6a3c5bc6-9dce-48…

3. Verdict

APPROVE — The diff cleanly centralizes B1 canonical-form authority in std/node.dag, makes the contract deterministic and decidable, and avoids new implementation surface or untracked debt. I found no diff-cited invariant violation; the remaining proof burden belongs to the future T-1 implementation/tests of the ratified contract.

@briansrls

Copy link
Copy Markdown
Contributor Author

Valid — fixed (verified against current code, not stale-dismissed). Point 2 did state, under canonical : Node -> Node, that "A BOUND occurrence is replaced by its binder-relative De Bruijn position" — and you're right: Atom { identity: Symbol } is the only identifier carrier, there is no De Bruijn Node, and adding one is an A1 closed-kernel STOP, so that rewrite is unrepresentable (the α-fact had no Node carrier — INVARIANTS P2/P4).

Fix: α-equivalence is reframed as a property of the merkle_fold in content_hash = merkle_fold ∘ canonical, not a canonical-Node rewrite. The fold tracks binder scope as it descends and hashes a bound occurrence binder-relatively (a free Symbol by opaque K-1 identity); the De Bruijn position is a transient fold-local value contributing to a Hash — never a Node that would need a carrier. So the α-fact lives where it can (the fold, whose codomain is Hash, not Node) — representable, no carrier gap. canonical : Node -> Node now claims only the representable normalizations (post-normalize domain, child order). node.dag point-2 + the DECISIONS B1-CANON prose are harmonized; the canonical/content_hash = merkle_fold ∘ canonical contract signature is unchanged and now consistent.

— sent from valiant-boar-161

…-Node rewrite (briansrls #3157 BLOCKING)

Valid (verified current code). Point 2 said canonical:Node->Node
"A BOUND occurrence is replaced by its binder-relative De Bruijn
position" — but Atom{identity:Symbol} is the ONLY identifier carrier;
there is no De Bruijn Node and adding one is an A1 closed-kernel STOP,
so that rewrite is unrepresentable (the α-fact had no Node carrier;
INVARIANTS P2/P4).

Fix: De Bruijn / binder-relative identity is a property of the
`merkle_fold` in `content_hash = merkle_fold ∘ canonical`, NOT a
materialized canonical Node. The fold tracks binder scope and hashes
a bound occurrence binder-relatively (free Symbol by opaque K-1
identity); the De Bruijn position is a transient fold-local value
contributing to a Hash, never a Node — so the α-fact lives where it
CAN (the fold's Hash codomain), representable, no carrier gap.
`canonical:Node->Node` keeps only the representable normalizations
(domain, child order). node.dag point 2 + DECISIONS B1-CANON prose
harmonized.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Already addressed — verified against current code, not stale-dismissed. codex reviewed a85b22cb, which is 2 commits behind HEAD 600f1c53a. The same finding (the briansrls inline version at node.dag:107) was fixed in commit 600f1c53a last cycle, and that fix implements codex's exact prescribed remedy:

codex: "either add a substrate-representable bound-reference shape or restate content_hash as an environmented fold that does not claim to materialize canonical : Node -> Node."

The fix took the second option verbatim. Current node.dag point-2:

"α-EQUIVALENCE (K-1) — a merkle_fold property, NOT a canonical-Node rewrite … canonical : Node -> Node does NOT replace bound occurrences with De Bruijn positions (unrepresentable) … binder-relative identity lives in the FOLD of content_hash = merkle_fold ∘ canonical: the fold tracks binder scope as it descends … the De Bruijn position is a transient fold-local value contributing to a Hash — never a Node needing a carrier."

That is precisely an environmented (binder-scope-tracking) fold whose codomain is Hash, with canonical : Node -> Node no longer claiming to materialize the bound-reference structure. The DECISIONS B1-CANON prose was harmonized in the same commit. The representability gap is closed; no further change needed for this item.

— sent from valiant-boar-161

@briansrls
briansrls merged commit eabfa71 into main May 16, 2026
7 checks passed
briansrls added a commit that referenced this pull request May 16, 2026
…#3162)

* WIP: v4 design

* v4 D1: Diff/Path structural-delta vocabulary over Node

Side-session-ratified B-1. EdgeSelector/Path/Edit/Diff data types in
std/node.dag (DATA ONLY; A1-clean — Node stays the sole recursive type;
B1/Hash precedent for substrate-tier placement). Operations
(subterm_at/apply_diff -> Result<Node,Diagnostic>, paths_independent/
diff_well_formed) contracted into lens/application.dag header — forced
off node.dag by the diagnostic.dag->node.dag cycle (Correction=
Suggested(Node)); the node.dag "consumers fail-close" discipline.
affected_set.dag consumes the type. DECISIONS.md Part-1 row D1 carries
the data/op-split PROPOSED sub-point for operator confirmation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WIP: v4 design

* v4 D1: dissolve two P2 illegal-state surfaces (codex #3162 REQUEST_CHANGES)

Both findings valid; fixed structurally rather than patched.

Finding 1 (node.dag:~478 ByPosition{index:Int} admits negatives): drop
EdgeSelector/ByPosition entirely. Path = List<Symbol> — each step
descends the Named edge keyed by that opaque Symbol. No ordinal exists,
so the negative/out-of-range illegal state is structurally impossible
(dissolved, not deferred to path resolution). Positional-connective
children are reached by replacing the enclosing Named subtree (the safe
over-fire #3157 blesses); surgical positional addressing is a future
operator-ratified extension (consumes-nothing root has no Nat; recursive
Peano = A1 STOP).

Finding 2 (node.dag:~514 Diff=List<Edit> validity via external
predicate): drop the set / pairwise-independence / parallel-positions
framing and paths_independent/diff_well_formed. Diff = ORDERED List<Edit>
applied SEQUENTIALLY; any List<Edit> is a valid rewrite program (no
representable illegal state). A non-resolving Edit => fail-closed
Diagnostic at apply time (operational, not a type-level illegal state);
apply_diff is a fold, all-or-nothing.

Both refine the operator-ratified shape — flagged in DECISIONS.md D1
PROPOSED-for-operator-eye for confirm/redirect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 D1: add Practice-4 dissolution receipt; fix RATIFIED/PROPOSED contradiction (#3162 BLOCKING)

BLOCKING (codex via briansrls relay, vs pre-fix sha a06a066 — verified
against current HEAD):
- "D1 sum vocabulary without coproduct receipt": the EdgeSelector sum
  was already deleted in 7c91f73, but the DECISION not to have a
  positional selector is itself a Practice-4 dissolution requiring the
  formal 4-pattern ledger (algebra.dag precedent). Added the receipt to
  std/node.dag D1 (🟢 GREEN terminal: positional addressing subsumed by
  enclosing-Named-subtree replacement; not a deferred gap).
- "root-status driving the index carrier": resolved by the same
  dissolution — there is no index carrier; the receipt documents why
  none is needed (vs the reviewer's Nat/Fin suggestion) and why a
  future surgical form is an explicit operator-ratified extension.

Non-blocking: DECISIONS.md D1 row no longer carries a "confirm or
redirect" clause inside a RATIFIED row — refinements are stated as
encoded-via-PR-review (the normal channel), not an open fork.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 D1: fix stale EdgeLabel cross-reference in node.dag header (cursor #3162 editorial)

The D1 header block cited "lines ~221-230" for the no-stored-index
discipline, but adding the D1 header block shifted line numbers — that
range is now `Connective`. Re-anchored by construct name + corrected
approx lines: `type EdgeLabel` ~284 and its "deliberately NO stored
index" rationale ~278. Editorial only; cursor APPROVE on this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 B-4: affected_set R2 node-precise-resolution contract

Encodes the B-4 cross-cutting decision (operator-greenlit): affected_set
minimality requires 04_infer resolution to bind each use-site name-ref
to the EXACT declaration Node (not module/scope). The use->def graph is
derived by traversal over already-resolved name-references, NOT a 5th
InferredFacts field (IR-1-safe; same derive-don't-store discipline as
content_hash/D1). Coarse resolution = correct but maximally over-fires.

Reciprocal header contracts: 04_infer.dag B-4 block + affected_set.dag
Consumes note. DECISIONS.md Part-1 row B-4. Scaffold headers only; no
bodies. Continues the v4 design-survey on the session branch alongside
D1 (operator sequences/carves at manual merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 B-5 + subsume #3153 + B-2 (lean.dag PROOF-1 model)

B-5 (affected_set R1 usefulness): effect is a per-Arrow-Node fact from
the signature (B3), never aggregated coarse — a granularity invariant
(coarse ⇒ R1 correct-but-useless). effect.dag B-5 block + affected_set
Consumes/Ex.R1 cross-ref + DECISIONS B-5 row.

Subsume #3153 (v4-affected-set-design, no worker — operator-directed):
its 4 worked-examples + adversarial R1/R2/R3 + dogfood-reality folded
into affected_set.dag as the T-21 design contract, reconciled to the
now-ENCODED reciprocal contracts — Ex.R1→B-5 (effect.dag), Ex.R2→B-4
(04_infer.dag), Ex.R3→B-3 (drafted #3157 point-3 correction). #3153
superseded; recommend close. DECISIONS #3153-subsumed row.

B-2 (PROOF-1 prover model lands): extdeps/languages/lean.dag (new) —
Lean first, termination theorem class; Coq deferred second-prover
probe. B2-OMNI requires the LanguageModel file PROOF-1 #3158 deferred.
STRUCTURE enumeration+count 63→64; DECISIONS L-4; TASKS PROOF-1 note.

Includes the already-committed origin/main merge (204b855: #3158
PROOF-1 + #3160 meta-cut; DECISIONS.md conflict resolved keeping
D1/B-4/PROOF-1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WIP: v4 design

* v4 B-4: correct the use→def carrier (Symbol/K-1, not resolved_type) — #3162 BLOCKING

Valid P2/Practice-3 finding (briansrls inline @ 04_infer.dag:54):
resolved_type is the node's TYPE; many declarations share a type, so it
is lossy for binding identity — the use→def fact had no named forward
carrier. Corrected: the carrier is the post-resolution opaque Symbol
(K-1) on the name-ref Atom, AUTHORED by 03_resolve.dag (binding =
Symbol equality, node.dag name_occurrences pattern). 04_infer is
preserve-only (no 5th InferredFacts field, IR-1-safe); affected_set
derives by Symbol-equality traversal. Fixed consistently across
04_infer.dag B-4 block, new 03_resolve.dag B-4 authority note,
affected_set.dag Consumes note, DECISIONS.md B-4 row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WIP: v4 design

* WIP: v4 design

* v4 C5-2 emit-locality + T-9 infer keystone contracts (operator-ratified)

C5-2 (emit-locality): emit is structurally local — a D1 apply_diff at
Path p re-emits ONLY p's subtree. Per operator code-as-data steer:
locality is Node-relative, NOT file-relative; a file is a convenience
projection, the substrate anchors no semantic on the file concept;
non-local emit = declared normalization at the enclosing NODE scope,
never "file scope". The C5↔D1 hinge (AGENT-1 faithful re-emit).
05_emit.dag header + Owns + DECISIONS C5-2 row.

T-9 A+B: A — Find decidable by construction (closed declared-inhabitance
candidate set, not a synthesized search; structural descent ⇒ the
inferencer terminates; empty = decidable fail-closed Diagnostic).
B — single-file discipline is the 03_resolve scope boundary (env/scope/
lookup/binding belongs to 03_resolve per B-4; infer owns only Find +
cardinality + diagnostic precision). 04_infer.dag header + DECISIONS
T-9 row.

Receipts: in-header (operator decision; status quo, no change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 D1: complete the Practice-4 receipt — add Pattern 5 (#3162 codex REQUEST_CHANGES)

Valid finding. docs/modeling-discipline.md:112 requires FIVE dissolution
patterns; the node.dag #3160 operator-ratified receipts already use 5.
The D1 receipt did only 4 (I wrongly followed algebra.dag's older
4-pattern style). Added Pattern 5 (Parameterized family) — FAILS:
ByName/ByPosition are not one mechanical F<X> over a declared set
(different payload types + addressing semantics; mirror EdgeLabel's
genuine 2-variant discriminant, not an enumerated copy à la
Homomorphism-per-algebra); no source set to project over (Practice-7
N/A); post-dissolution one constructor = not a family. "four" → "five".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4 C5-1 + C5-3 (operator-ratified) — node-level round-trip law + spec-provable F

C5-1: the bidirectional_roundtrip TestClaim asserts ingest∘emit=id_Node
via B1 content_hash (meaning round-trips), NOT text-byte identity.
Text-level fidelity recorded as a DEFERRED orthogonal future layer (a
trivia sidecar outside Node/content_hash, partial; the C5-fidelity
Declared-normalized dispositions are its forward hook — node-level
adoption preserves it zero-rework). Non-orthogonal route (lexical in
the Node) explicitly rejected.

C5-3: F = the spec-provable meaning core; spec silence ⇒ out of F
(Declared-normalized or Fail-closed). Conservative/fail-safe — the
spec decides, modeler has no discretion; per-language non-F shrinkage
declared up front, not discovered at test time.

Encoded: lens/testgen.dag C5-1/C5-3 header block + DECISIONS C5-1/C5-3
rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v4: reconcile PROOF-1 row with L-4 — DECISIONS.md self-consistency (cursor #3162 APPROVE_WITH_COMMENTS)

Valid NON-BLOCKING finding: B-2 added lean.dag + the L-4 row but the
PROOF-1 row still ended "**NO new file** (closed-tree invariant)",
contradicting L-4 (which lands extdeps/languages/lean.dag). Reconciled:
PROOF-1's framing (#3158) added no file (framing-only, deferred the
model "lands later"); that deferred model subsequently lands as
lean.dag per B-2/L-4. One coherent story; PROOF-1 row now cross-refs
L-4 and states "No contradiction" explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review metadata

  • Provider / model: codex / unknown
  • Commit: 600f1c53 · Trigger: schedule
  • Thinking: 171s wall

BLOCKING (1)

Root Cause

  • src/v4/std/node.dag α-equivalence moved into an environmented fold while the B1 Merkle contract stayed context-free → state the binder-environment key in the fold/dedup contract or narrow “hash once” to closed sub-Nodes.

Non-blocking — Strengths

  • src/v4/DECISIONS.md The ledger now correctly makes B1-CANON the in-header authority and keeps T-1 as implementer rather than designer.

⚠️ One load-bearing Merkle/dedup contract issue remains before this canonical-form clause is safe for downstream T-1/T-21 consumers.

Comment thread src/v4/std/node.dag
// it terminates by Node finiteness and is decidable
// (feedback_decidability_invariant; the same A2-implicit descent
// `node_well_formed` uses). `content_hash = merkle_fold ∘
// canonical`; structurally-shared sub-Nodes hash ONCE — content-

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

BLOCKING: B1-CANON makes bound-occurrence hashing depend on the fold's binder environment, so claiming structurally shared sub-Nodes hash once as a context-free Merkle consequence is unsound under INVARIANTS P2/P4 unless the environment is part of the fold/memo key or dedup is limited to closed sub-Nodes.

@briansrls
briansrls deleted the v4-canonical-form branch June 1, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant