feat(#92): widths need not agree for ==, and match arms join on width - #95
Conversation
Implements the #92 ruling across both engines, plus two bugs found while verifying it. ## The rule `T-Eq-Word` required both operands at the same width. That was inconsistent with the rest of the language in four ways, all recorded in #92: * `tComposeWord` already WIDENS to max n m — a program could compose two braids it was then forbidden to compare; * `~` accepted differing widths and, since TG-7, evaluates through the SAME `Braid_equiv.equiv` — identical operands, identical answer, one rejected; * `eqIdBraid`/`eqBraidId` decide "is this braid trivial?" against `identity : word 0`, so under the old rule they could only ever fire when the braid was empty — the step relation had rules for a question the typing rule forbade asking; * `braid_equiv.equiv` has no width parameter at all. Lean `tEqWord` now takes (n m); `infer`'s `.eq` case drops the `if n = m`. OCaml `Eq` mirrors it. Match arms join on width for words (max) and still require exact agreement for everything else. ## The metatheory did not need patching Progress, Preservation, Determinism, TypeSafety, infer_sound and infer_complete all still hold — `lean Tangle.lean` reports 0 errors, and the sorry/axiom gate passes. The `infer_sound` proof got SIMPLER: with no `if n = m` there is no inner `split` left to case on, which is a decent sign the restriction was load-bearing for nothing. TG-3 regenerated and kernel-checked: 496 obligations, 0 errors. 46 of them flipped from `= none` to `= some .bool` — precisely the `identity == braid` shape that was previously unreachable. ## Two bugs found while verifying 1. STATEMENT ORDER. `parse_file_recovering` in bin/main.ml did `stmts := prog @ !stmts` and then `List.rev` on the flattened result. That reversal is right for an accumulator built by prepending single items — as `diagnostics` is — but whole SEGMENTS were prepended, so every program came out backwards: `def x; def y; def z` parsed to [z; y; x]. Any program whose statements depend on order failed at evaluation with "Unbound variable". The test suites never caught it because they call `Tangle.Parser.program` directly; only the CLI goes through the recovering path. Now accumulates segments and flattens in order. 2. A FALSE ASSERTION in examples/trefoil.tangle: assert reversed == braid[s1, s1, s1] `reverse` reverses the word AND negates exponents, so it yields the INVERSE braid. writhe(trefoil) = 3 but writhe(reversed) = -3, and writhe is invariant under the braid relations, so they cannot be equal. The assertion had been wrong since it was written; nothing ran the examples until #89. Corrected to braid[s1^-1, s1^-1, s1^-1], which passes. ## Result examples evaluating : 2/7 -> 7/7 (all now in the must-run set) lib/stdlib.tangle : did not typecheck -> typechecks conformance : 18/19 (v11 add{} is the Harvard sub-language, #94) ## Trusted base Registered as A-TG-92.1: the embedding Bn -> Bn+1 that justifies cross-width comparison is standard mathematics but is asserted IN PROSE, not mechanised. What is machine-checked is that the metatheory holds under the widened rule and that OCaml still agrees with Lean on the corpus. Tests: 6 new typecheck cases including two negatives (mismatched KINDS still rejected; match arms of different kinds still rejected) so widening cannot degrade into "anything compares to anything". Three test_check cases repointed at an error that is still an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Note Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime. Code Review✅ ApprovedRemoves strict width agreement requirements for equality comparisons and adds match arm width joining across both engines, enabling all 7 example programs to evaluate successfully. No issues found.
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
…ertion (#97) The false assertion corrected in #95 was **not an isolated slip**. Auditing whether the conformance corpus actually *runs* — the gate only ever checked that it *parsed* — turned up a second mathematically false claim of exactly the same kind, plus one genuine unimplemented feature. ## The second false assertion `conformance/valid/v16_close_mirror_reverse.tangle`: ```tangle assert reverse(braid[s1, s2]) == braid[s2, s1] ``` `reverse` reverses the word **and** negates every exponent — it yields the *inverse* braid, so `reverse(s1 s2) = s2⁻¹ s1⁻¹`. Disproved by invariant: `writhe(reverse(braid[s1,s2])) = -2` while `writhe(braid[s2,s1]) = +2`, and writhe is invariant under the braid relations, so they cannot be the same element. ## Two authors, same mistake → a documentation failure `examples/trefoil.tangle` and this file made the *identical* wrong assumption. That is not carelessness. `FORMAL-SEMANTICS.md` described the operation as: ``` | reverse(e) -- reverse word ``` which reads as order-only. Now stated as an identity: ``` reverse(g₁ g₂ … gₙ) = gₙ⁻¹ … g₂⁻¹ g₁⁻¹ i.e. reverse(w) = w⁻¹ ``` with the contrast against `mirror` (which negates **in place**) spelled out, and the writhe argument recorded so the next reader can check it rather than trust it. ## The systemic fix `scripts/check-corpus.sh` now **evaluates** `conformance/valid`, not just parses it. Parsing was never enough — a program can parse perfectly and assert something false. Both bad assertions survived precisely because nothing ran them. **Verified in both directions:** reintroducing the exact false assertion makes the gate fail (`exit 1`, *"parses but does not evaluate"*); restoring it returns `exit 0`. ## The genuine gap `v09_twist.tangle` uses `(~a)` to twist a named strand inside a weave. `spec/grammar.ebnf` says *"In weave context: (~a) twists named strand a"*, but `infer_expr` rejects any strand name used as an expression — specified and unimplemented. Recorded in the new `CONFORMANCE_KNOWN_UNRUNNABLE` list rather than papered over, and raised as **#96**. Worth noting *why* it was invisible: until #93 a weave block could not be bound to anything, so its body was never evaluated. The construct has literally never run. ## Regression tests Four pinning `reverse`, chosen so they cannot pass vacuously: | Test | Why it can't be faked | |---|---| | reverse is **not** order-reversal | asserts inequality with the wrong answer | | `reverse(reverse(w)) = w` | only holds if it genuinely inverts | | reverse negates writhe | the invariant that disproved both false claims | | `mirror` negates *in place* | pins the contrast that caused the confusion | Suites 112 → 116, all green; TG-3 still 1008/0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ent-order bug (#99) TG-11 (#98) landed the type former, typing, evaluation and six proofs — but there was **no way to write one**. Zero occurrences of the constructors in `lexer.mll` or `parser.mly`, against 4 for echo. Fully implemented and unreachable. ## Surface syntax ```tangle def w = warrant[0](42, braid[s1, s2]) -- at standpoint 0 def token = evidence(w) -- the ONLY elimination ``` The standpoint is bracketed because it's an *index*, not an operand — the same reading as a braid generator's subscript. No grammar conflicts. **Non-factivity reaches the syntax.** There is deliberately no keyword extracting the claim: `claim(w)` parses as an ordinary call to an undefined function, never as a language form. A test pins that distinction — if a `Claim` form ever appears, it contradicts `epi_only_yields_evidence`. ## A *second* copy of the statement-order bug Writing the first epistemic program surfaced it immediately: ``` def w = warrant[0](42, braid[s1]) def tok = evidence(w) → In definition 'tok': evidence requires an Epi[k, rho, tau], got Word[0] ``` That `Word[0]` is the pass-1a **placeholder** — `tok` was being checked *before* `w` was refined. Same root cause as #95 (`stmts := prog @ !stmts` then `List.rev` on the flattened list), but in a **second copy**, in `lib/check.ml`. **That copy feeds `tanglec --check` and the LSP.** Both have been analysing programs with their statements reversed. And it was never epistemic-specific: ``` def e = echoClose(braid[s1]) def r = residue(e) → residue requires Echo[_, _], got Word[0] ``` **Any cross-definition reference to a non-`Word` type was mis-typed.** Echo has been broken this way for as long as the recovering path has existed. The test suites missed *both* copies for the same reason: they call `Parser.program` directly and never exercise the recovering path. ## Tests **Parser** — warrant with bracketed standpoint; evidence; TG-4 round-trip; the no-claim-form guard. **Check** — three cross-definition cases (plain, echo, epistemic), all of which would have failed before this. ## Example `examples/epistemic.tangle`, wired into the corpus gate's must-run set — warrants at three standpoints, evidence recovery, and the echo composition `residue(evidence(we))`. All assertions pass. It also documents *why* the claim is unreachable: if it were extractable, anything anyone attested would become true by fiat — precisely the bug you don't want in a provenance system. All gates green: Lean 0 errors, full suite, corpus, RSR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Implements the #92 ruling across both engines — plus two bugs found while verifying it.
The rule
T-Eq-Wordrequired both operands at the same width. Four ways that was inconsistent with the language itself (evidence in #92):tComposeWordalready widens tomax n m— you could compose two braids you were then forbidden to compare.~accepted differing widths and, since TG-7, evaluates through the sameBraid_equiv.equiv. Identical operands, identical answer, one rejected by the typechecker.eqIdBraid/eqBraidIddecide "is this braid trivial?" againstidentity : word 0, so under the old rule they could only fire when the braid was empty. The step relation had rules for a question the typing rule forbade asking.braid_equiv.equivhas no width parameter at all.Lean's
tEqWordnow takes(n m);infer's.eqcase drops theif n = m; OCamlEqmirrors it. Match arms join on width for words and still require exact agreement otherwise.The metatheory needed no patching
Progress, Preservation, Determinism, TypeSafety,
infer_sound,infer_completeall still hold —lean Tangle.leanreports 0 errors, sorry/axiom gate passes.infer_sound's proof got simpler: with noif n = mthere's no innersplitto case on. A restriction whose removal shortens the proof was carrying no weight.TG-3 regenerated and kernel-checked: 496 obligations, 0 errors. 46 flipped from
= noneto= some .bool— exactly theidentity == braidshape that was unreachable before.Two bugs found while verifying
1. Statement order.
parse_file_recoveringdidstmts := prog @ !stmtsthenList.revon the flattened result. That reversal is correct for an accumulator built by prepending single items (asdiagnosticsis) — but whole segments were prepended, so every program came out backwards:Any program whose statements depend on order died with "Unbound variable". The test suites never caught it because they call
Tangle.Parser.programdirectly — only the CLI goes through the recovering path.2. A false assertion in
examples/trefoil.tangle:reversereverses the word and negates exponents, yielding the inverse braid. Verified by invariant:writhe(trefoil) = 3,writhe(reversed) = -3, and writhe is invariant under the braid relations — so they cannot be equal. The assertion had been wrong since it was written; nothing ran the examples until #89. Corrected tobraid[s1^-1, s1^-1, s1^-1], which passes.Result
lib/stdlib.tanglev11is the Harvard sub-language, #94)All seven examples are now in the corpus gate's must-run set, so none can regress.
⚠ Trusted base — registered as A-TG-92.1
The embedding Bₙ ↪ Bₙ₊₁ that justifies cross-width comparison is standard mathematics, but it is asserted in prose, not mechanised — no Lean lemma states it.
What is machine-checked: that the metatheory holds under the widened rule, and that OCaml
infer_exprstill agrees with Leaninferon the corpus. That distinction is recorded inASSUMPTIONS.mdrather than left implicit.Tests
6 new typecheck cases, including two negatives — mismatched kinds still rejected, match arms of different kinds still rejected — so widening cannot quietly become "anything compares to anything". Three
test_checkcases repointed at an error that is still an error (and an assertion helper left unused by the change was put back to work rather than deleted).🤖 Generated with Claude Code