Skip to content

Commit 1403f2a

Browse files
fix(corpus): run conformance programs, and correct a second false assertion (#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>
1 parent 478857f commit 1403f2a

4 files changed

Lines changed: 97 additions & 3 deletions

File tree

compiler/test/test_eval.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,32 @@ let test_reverse () =
205205
eval (Reverse (BraidLit [sigma 1; sigma 2]))
206206
= VBraid [rgen 2 (-1); rgen 1 (-1)]);
207207

208+
(* Reverse is the INVERSE braid: order reversed AND exponents negated.
209+
Two separate corpus programs (examples/trefoil.tangle and
210+
conformance/valid/v16) asserted that it merely reverses order. Both were
211+
mathematically false and both went undetected for want of anything that
212+
ran them. These pin the semantics so the misunderstanding cannot return. *)
213+
test "reverse is the INVERSE, not just order-reversal" (fun () ->
214+
(* reverse(s1 s2) = s2^-1 s1^-1, NOT s2 s1 *)
215+
eval (Reverse (BraidLit [sigma 1; sigma 2]))
216+
<> VBraid [rgen 2 1; rgen 1 1]);
217+
218+
test "reverse composed with itself is the identity map" (fun () ->
219+
(* (w^-1)^-1 = w — a property that only holds if reverse really inverts. *)
220+
let w = BraidLit [sigma 1; sigma 2; sigma_inv 1] in
221+
eval (Reverse (Reverse w)) = eval w);
222+
223+
test "reverse negates writhe (the invariant that disproved the false claims)" (fun () ->
224+
match eval (BraidLit [sigma 1; sigma 1; sigma 1]),
225+
eval (Reverse (BraidLit [sigma 1; sigma 1; sigma 1])) with
226+
| VBraid a, VBraid b -> writhe a = 3 && writhe b = -3
227+
| _ -> false);
228+
229+
test "mirror negates IN PLACE, unlike reverse" (fun () ->
230+
(* The contrast that makes the two easy to confuse. *)
231+
eval (Mirror (BraidLit [sigma 1; sigma 2]))
232+
= VBraid [rgen 1 (-1); rgen 2 (-1)]);
233+
208234
(* Reverse identity is identity *)
209235
test "Reverse identity" (fun () ->
210236
eval (Reverse Identity) = VBraid []);

conformance/valid/v16_close_mirror_reverse.tangle

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@
33

44
def trefoil = braid[s1, s1, s1]
55

6+
# mirror negates each exponent IN PLACE, preserving order.
67
assert mirror(trefoil) == braid[s1^-1, s1^-1, s1^-1]
7-
assert reverse(braid[s1, s2]) == braid[s2, s1]
8+
9+
# reverse reverses the word AND negates each exponent — it yields the INVERSE
10+
# braid, so reverse(s1 s2) = s2^-1 s1^-1, not s2 s1.
11+
#
12+
# This file previously asserted `== braid[s2, s1]`, which is false:
13+
# writhe(reverse(braid[s1,s2])) = -2 but writhe(braid[s2,s1]) = +2, and writhe
14+
# is invariant under the braid relations, so they cannot be the same element.
15+
# The same wrong assumption about `reverse` was also in examples/trefoil.tangle.
16+
# Neither was caught because nothing ever RAN these programs — the conformance
17+
# gate only checked that they parsed.
18+
assert reverse(braid[s1, s2]) == braid[s2^-1, s1^-1]
819

920
compute writhe(close(trefoil))

docs/spec/FORMAL-SEMANTICS.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ e ::= x -- variable reference
5757
| cap -- cap primitive
5858
| cup -- cup primitive
5959
| mirror(e) -- mirror image
60-
| reverse(e) -- reverse word
60+
| reverse(e) -- inverse braid (see §Reverse)
6161
| simplify(e) -- Reidemeister simplification
6262
| f(e₁, ..., eₖ) -- function application
6363
| match e with arm₁ | ... | armₖ end -- pattern matching
@@ -372,7 +372,22 @@ With explicit strand types (in weave context):
372372
Γ ⊢ mirror(e) : Word[n]
373373
```
374374

375-
**Reverse** — inverts all generators in a word:
375+
**Reverse** — the INVERSE braid. It reverses the word **and** negates every
376+
exponent:
377+
378+
```
379+
reverse(g₁ g₂ … gₙ) = gₙ⁻¹ … g₂⁻¹ g₁⁻¹ i.e. reverse(w) = w⁻¹
380+
```
381+
382+
So `reverse(braid[s1, s2]) = braid[s2^-1, s1^-1]` — NOT `braid[s2, s1]`.
383+
384+
This is easy to get wrong, and was: both `examples/trefoil.tangle` and
385+
`conformance/valid/v16_close_mirror_reverse.tangle` asserted that `reverse`
386+
merely reverses order, which is false. `writhe` disproves it — writhe is the
387+
exponent sum and is invariant under the braid relations, so `w` and `w⁻¹`
388+
differ in writhe by twice the writhe of `w` and cannot be equal unless the
389+
writhe is 0. Contrast **mirror**, which negates exponents *in place* and
390+
leaves the order alone.
376391

377392
```
378393
Γ ⊢ e : Word[n]

scripts/check-corpus.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ CONFORMANCE_KNOWN_UNPARSED=(
7373
v11_add_block.tangle
7474
)
7575

76+
# conformance/valid programs that PARSE but do not yet TYPECHECK+EVALUATE.
77+
# v09_twist : `(~a)` — twisting a named strand inside a weave. spec/grammar.ebnf
78+
# says "In weave context: (~a) twists named strand a", but
79+
# infer_expr rejects any strand name used as an expression, so the
80+
# construct is specified and unimplemented. Tracked in #96.
81+
# v11 : does not parse at all (see above); listed here too so the eval
82+
# loop does not double-report it.
83+
CONFORMANCE_KNOWN_UNRUNNABLE=(
84+
v09_twist.tangle
85+
v11_add_block.tangle
86+
)
87+
7688
fail=0
7789
note() { printf ' %-34s %s\n' "$1" "$2"; }
7890

@@ -149,6 +161,36 @@ for f in "${ROOT}"/conformance/valid/*.tangle; do
149161
fi
150162
fi
151163
done
164+
# conformance/valid must also RUN, not merely parse.
165+
# Parsing alone was never enough: `examples/trefoil.tangle` and
166+
# `conformance/valid/v16_close_mirror_reverse.tangle` BOTH asserted
167+
# `reverse(w) == <w with order reversed>`, forgetting that `reverse` also
168+
# negates exponents and so yields the inverse braid. Both were mathematically
169+
# false — disproved by writhe, which is invariant under the braid relations —
170+
# and both sat undetected because the gate only checked that they parsed.
171+
echo "== conformance: valid programs must EVALUATE =="
172+
for f in "${ROOT}"/conformance/valid/*.tangle; do
173+
n="$(basename "$f")"
174+
if "${BIN}" --eval "$f" >/dev/null 2>&1; then
175+
if in_list "$n" "${CONFORMANCE_KNOWN_UNRUNNABLE[@]}"; then
176+
note "valid/$n" "NOW RUNS"
177+
echo "::error::conformance/valid/${n} now evaluates — drop it from CONFORMANCE_KNOWN_UNRUNNABLE."
178+
fail=1
179+
else
180+
note "valid/$n" "evaluates ok"
181+
fi
182+
else
183+
if in_list "$n" "${CONFORMANCE_KNOWN_UNRUNNABLE[@]}"; then
184+
note "valid/$n" "still not runnable (expected)"
185+
else
186+
note "valid/$n" "EVAL FAILED"
187+
echo "::error::conformance/valid/${n} parses but does not evaluate:"
188+
"${BIN}" --eval "$f" 2>&1 | head -3
189+
fail=1
190+
fi
191+
fi
192+
done
193+
152194
for f in "${ROOT}"/conformance/invalid/*.tangle; do
153195
n="$(basename "$f")"
154196
if "${BIN}" "$f" >/dev/null 2>&1; then

0 commit comments

Comments
 (0)