feat(tg-7): == on braids decides braid-group equivalence (#50 ruling) - #87
Conversation
Implements the owner ruling on #50: option (a), true braid-group equivalence. Syntactic equality contradicted the language's own thesis — programs are topological objects and equivalence is isotopy — so `==` was the one place that quietly reverted to comparing representations. `σ₁σ₂σ₁ == σ₂σ₁σ₂` is now true. Landed on BOTH engines in one change, because the TG-3 differential and the Lean proof oracle straddle them: * OCaml — `eval.ml` `Eq` on VBraid routes through `braid_equiv.ml` (Dehornoy handle reduction, already present and tested). `Identity` is `VBraid []`, so identity comparisons flow through the same case. Added `ast_of_gen(s)`, the missing inverse of the existing `gen_of_ast` (the runtime and AST generator records are structurally identical but nominally distinct). * Lean — new §BRAID-GROUP EQUIVALENCE in `Tangle.lean` ports the same procedure (`unitsOf`, `inverseWord`, `freeReduce`, `scanHandle`, `rewriteInterior`, `reduceOne`, `reduceFuel`, `braidEquiv`). Six Step rules now use it: `eqBraids`, `eqIdBraid`, `eqBraidId` and the three `echoEq` counterparts — echoEq must follow because eval.ml's EchoEq explicitly reuses the Eq logic. `~` (isotopy) routes there too. It used `simplify_gens`, which only cancels adjacent inverses and does NOT implement the braid relation. Leaving that while `==` became complete would have allowed `a == b` true with `a ~ b` false, which is incoherent — for braids, isotopy IS braid-group equality. This is a completeness fix, not a change of intended meaning. Closed tangles are documented as a sound-but-incomplete under-approximation: link isotopy is Markov equivalence, which is NOT implemented. Progress / Preservation / Determinism re-verified UNCHANGED — all three need only that the RHS is a total function into Bool. TG-3's 496 kernel-checked obligations still pass (typing is untouched; only the runtime value changes). TRUSTED, NOT PROVEN. These are definitions, not axioms, so the sorry/axiom gate passes legitimately — but that does not make the claim proven. The metatheory holds *relative to* `braidEquiv`; that `braidEquiv` correctly DECIDES braid-group equality is the mechanised Garside/Dehornoy proof, which remains research-grade (#51). Recorded as new assumption A-TG-7.2, and stated plainly in PROOF-NARRATIVE.md so the green gate cannot be mistaken for a proof. The Lean port is fuel-bounded rather than well-founded, since proving termination IS that research obligation. Tests: 8 new cases in test_eval.ml that DISTINGUISH the two semantics (the pre-existing braid-equality tests pass either way, so they could not). They cover the braid relation, far commutation, free cancellation, symmetry, and two negatives guarding against a procedure that just says true. eval suite 104 -> 112, whole suite 0 failures. Also in this change, both found while doing the above: * `.github/workflows/ocaml-ci.yml` (NEW) — the OCaml compiler and its 4,000+ assertions were NOT built or run by CI at all; only the Lean proofs were gated. A semantics change to eval.ml could have landed fully green with nothing having compiled it. Uses apt rather than a setup action so no new third-party pin enters the estate. * `lean-proofs.yml` — the sorry/axiom gate's comment filter never worked: it anchored on `^\s*--` AFTER `grep -n` had prepended `<line>:`, so the documented "outside comments" exemption could never match. It went unnoticed only because no comment had yet contained one of these words; the TG-7 trusted-base note does. Anchored after the prefix — strictly tighter than the old unanchored `/-` catch-all, and verified to still catch a real `sorry`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gate's first run went red on a test suite that reported ZERO failures. The FAIL-marker check was case-insensitive and unanchored on the right, so `^[[:space:]]*FAIL` matched the compositional suite's "Failed: 0" summary line. A gate that cries wolf is worse than no gate — it trains maintainers to ignore it. Now requires a FAIL *marker*: "FAIL" followed by ':' or whitespace, matched case-sensitively. Verified in both directions against the real test output: no false positive on a passing run, still catches an injected " FAIL Braid equality true", and "Failed: 0" is correctly ignored. The tally check also now covers the "Failed: N" shape it previously missed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
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
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Gitar
`examples/` and `conformance/` existed but **were executed by nothing** — not CI, not the Justfile. ## What that cost `examples/isotopy.tangle` — the file whose entire purpose is to demonstrate the language's central claim, *"two braids can be structurally different but topologically equivalent"* — **was failing its own assertions.** The culprit is line 28: ```tangle assert simplify(braid[s1, s3, s1^-1]) ~ braid[s3] ``` That needs **far commutation** (|1−3| ≥ 2), which the old free-reduction `~` could not do. It began passing only when TG-7 (#87) made `~` decide real braid-group equivalence. Verified rather than assumed — built the pre-TG-7 commit and ran it: ``` === isotopy.tangle under PRE-TG-7 build === Runtime error: Assertion failed EXIT=1 ``` So the showcase example had been broken for as long as `~` used free reduction, and nothing noticed because nothing ran it. That is the case for this gate, and incidentally a nice independent validation of TG-7. ## The gate is a ratchet Five examples don't typecheck and five `conformance/valid/` programs don't parse. Those are **real gaps** (filed as #88), and they can't be fixed in the change that introduces the gate. So each is recorded explicitly *with its cause*, and the gate enforces in **both** directions: - nothing currently working may break; - nothing may join the known-bad lists; - **anything that starts working must be removed from them** — a stale entry fails the build. That last rule is what stops an "expected failures" list decaying into a permanent excuse. **Verified adversarially** — I tried to break it three ways, and it caught all three: | Attack | Result | |---|---| | Break a must-run example (append a false assert) | `EVAL FAILED` → exit 1 | | Add an unlisted example | `UNLISTED` → exit 1 | | Mark a *working* example as known-bad | `NOW WORKS` → exit 1 | ## Also: the conformance runner never worked `run_conformance.sh` invoked `dune exec -- tangle --parse-only`. **Both halves were wrong** — the executable is `main` (`compiler/bin/dune`), not `tangle`, and there has never been a `--parse-only` flag. Every invocation failed, so the suite reported **3/19** while proving nothing: all three "passes" were `invalid/` cases, and an invalid case passes *precisely when the command fails*. It failed for the wrong reason and scored a point for it. Fixed (prefers the pre-built binary, falls back to `dune exec`). The real number is **14/19**. ## Current state locked in | | | |---|---| | examples parse | **7/7** | | examples typecheck + evaluate | 2/7 (`echo_pd`, `isotopy`) | | conformance | **14/19** (was a fake 3/19) | | `invalid/` correctly rejected | 3/3 | Path filters were widened to `examples/**`, `conformance/**` and the script itself — otherwise a change to the corpus would never trigger the gate that checks it. Adds `just corpus` and `just check-all` for local use. Debt tracked in **#88**, with per-file causes and a suggested order. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Closes both acceptance criteria of #56 — though the second was already satisfied, and the first was mis-scoped. ## Criterion 1 — template residue The issue recorded *"5 `{{PLACEHOLDER}}` files + 8 `rsr-template-repo` references"*. Re-measured: **8** files matched `{{...}}` and **21** lines matched the template name — but most were **meta-references**: files *describing* the debt, not carrying it. Three would have been damaged by a naive search-and-replace: | File | Why it must not be touched | |---|---| | `src/rust/src/eval.rs` | `"...add{{}} block"` is a Rust `format!` **escape** — `{{}}` renders as a literal `{}`, naming the `add{}` language construct. Working code. | | `methodology.a2ml` | The token appears inside `reject-if-contains` — the rule that *forbids* it. | | `AFFIRMATION.adoc`, `docs/identity-fabric/` | Prose recording the debt. | **The genuine residue, now cleared:** `{{DEPS}}`, `{{BUILD_OUTPUT_PATH}}`, `{{PACKAGE_NAME}}`, `{{LANG_STACK}}`, `{{BUILD_CMD}}`, `{{TEST_CMD}}`, `{{MUST_INVARIANTS}}`, `{{PROJECT_UNIQUE_STRENGTH}}` — plus four contractiles and the citation guide whose **subject was still the template**. `Intentfile` asserted *"This repository is the canonical template for Rhodium Standard Repository compliance"*; `CITATIONS.adoc` credited *"Polymath, Hyper"* for *"RSR-template-repo"* in all five citation formats. ## A worse class, found while fixing it All three QUICKSTARTs documented commands that **do not exist** — `just build`, `just test`, `just lint`, `just panic-scan`, `just setup-dev`, `just build-release`, `just install`, `just run`, `just heal`, `just setup`, `just uninstall`, `just stapeln-run` — asserted an `$XDG_CONFIG_HOME/tangle/config.toml` the compiler never reads, and carried a *"Multi-Instance Deployment"* section for what is a single stateless binary. Documentation that fails when followed is worse than none. Every command now shown is verified to exist, and the worked example shows the **real** output (8 `assertion passed` lines, exit 0) rather than the template's invented `Tangle started successfully.` ## Criterion 2 — already satisfied by #78 `README.adoc` carries the correction and `AFFIRMATION.adoc` has *"Erratum — 2026-07-21 / Correction 1 — the stack does not exist"*. Worth noting: the criterion as worded — *"does the simplified diagram supersede, or re-state, the nuance?"* — presupposed the stack was real. **Both options were wrong.** `KRL → TangleIR → …` describes a dependency that never existed (`TangleIR` appears in no source file in either repo). It was withdrawn, not reconciled. ## Making the rule real `methodology.a2ml` has always declared `reject-if-contains = [..., "rsr-template-repo"]` — and **nothing enforced it**, which is precisely how the contractiles stayed mis-instantiated. Adds `scripts/check-rsr-instantiation.sh` (plus `just rsr-check` and a CI step), using an explicit allowlist so meta-references and the Rust escape aren't false-positived. **Verified adversarially:** | Attack | Caught | |---|---| | Reintroduce a `{{PLACEHOLDER}}` | ✅ | | Claim to be `rsr-template-repo` | ✅ | | Document a phantom `just` recipe | ✅ | | Rust `format!` escape `add{{}}` | ✅ correctly **ignored** | It earned itself immediately: on first run it flagged `QUICKSTART-USER.adoc`, a file I had not inspected, with six phantom recipes. ## Also `AFFIRMATION.adoc` gains a dated **Erratum — 2026-07-29** (Corrections 3–5) recording the residue clearance, that the OCaml compiler is no longer unbuilt (#87/#89 — the previous erratum's closing line said it was), and that `==` is no longer list equality (#50/#87). All gates pass locally: build, corpus, rsr-check, `must-check`, `trust-verify`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Implements the owner ruling on #50 — option (a) true braid-group equivalence.
σ₁σ₂σ₁ == σ₂σ₁σ₂is now true. Syntactic equality contradicted the language's own thesis (programs are topological objects; equivalence is isotopy), so==was the one place that quietly reverted to comparing representations.Both engines, one change
The TG-3 differential and the Lean proof oracle straddle OCaml and Lean, so they move together.
OCaml —
eval.mlEqonVBraidroutes through the existing, already-testedbraid_equiv.ml(Dehornoy handle reduction).IdentityisVBraid [], so identity comparisons flow through the same case. Addedast_of_gen(s), the missing inverse of the existinggen_of_ast.Lean — new §BRAID-GROUP EQUIVALENCE in
Tangle.leanports the same procedure. Six Step rules use it:eqBraids,eqIdBraid,eqBraidId, and the threeechoEqcounterparts —echoEqmust follow, becauseeval.ml'sEchoEqexplicitly reuses theEqlogic.~(isotopy) moved too — a completeness fix~usedsimplify_gens, which only cancels adjacent inverses and does not implement the braid relation. Leaving it while==became complete would allowa == btrue witha ~ bfalse — incoherent, since for braids isotopy is braid-group equality. So this is a completeness fix, not a change of intended meaning.Closed tangles are documented honestly: link isotopy is Markov equivalence (conjugation + stabilisation), which is NOT implemented. On
tv_closedtangles this stays a sound-but-incomplete under-approximation —trueis trustworthy,falseonly means "not equal as braids".Metatheory: unchanged, and that is the point
Progress / Preservation / Determinism re-verified with 0 errors, no proof edits. All three need only that the right-hand side is a total function into
Bool. Determinism is immediate: a function applied to fixed arguments yields a fixed result. TG-3's 496 kernel-checked obligations still pass — typing is untouched; only the runtime value changes.⚠ Trusted, not proven
These are definitions, not axioms, so the sorry/axiom gate passes legitimately — but that does not make the claim proven. The metatheory holds relative to
braidEquiv. ThatbraidEquivcorrectly decides braid-group equality is the mechanised Garside/Dehornoy proof, still research-grade (#51).Recorded as new assumption A-TG-7.2 and stated plainly in
PROOF-NARRATIVE.md, so a green gate cannot be mistaken for a proof. The Lean port is fuel-bounded rather than well-founded, because proving termination is that research obligation.Tests that actually distinguish the semantics
The pre-existing braid-equality tests pass under either semantics, so they could not demonstrate this change. Eight new cases in
test_eval.mldo:s1.s2.s1 == s2.s1.s2(braid relation)s1.s3 == s3.s1(far commutation)s1.s1^-1 == identityidentity == s1.s1^-1(symmetry)s1 == s1^-1(guard: differing writhe)s1.s2 == s2.s1(guard: adjacent don't commute)~agrees with==on the braid relationechoEqlowers to braid-group equalityTwo are deliberate negatives, guarding against a decision procedure that just returns
true. eval suite 104 → 112; whole suite 0 failures.Two defects found while doing this
1. The OCaml compiler was never gated.
dune build/dune testran nowhere in CI — the lexer, parser, typechecker, interpreter and 4,000+ assertions (TG-3 1008, TG-5 189, TG-7 2220, TG-8 2311, plus the rest) were entirely ungated. A semantics change toeval.mlcould have landed fully green with nothing having compiled it. Adds.github/workflows/ocaml-ci.yml, using apt rather than a setup action so no new third-party pin enters the estate (an unresolvable pin produces no check run at all).2. The sorry/axiom gate's comment filter never worked. It anchored on
^\s*--aftergrep -nhad prepended<line>:, so the documented "outside comments" exemption could never match — only the unanchored/-alternative did anything. It went unnoticed purely because no comment had yet contained one of those words; the TG-7 trusted-base note does. Now anchored after the prefix: strictly tighter than the old catch-all, and verified to still catch a realsorry(negative control).Verification
lean Tangle.lean→ 0 errorssorrycheck-tg3-differential.sh→ 496 obligations, 0 errorsdune build→ clean;dune test --force→ 0 failures across all suitesCloses the semantics half of #51.
🤖 Generated with Claude Code