Skip to content

Rule set termination is checked by the build (#746 tier 2) - #1057

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
test/rule-set-termination-is-a-gate
Aug 25, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
test/rule-set-termination-is-a-gate

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

#746 tier 2 asks for termination "checked by tooling rather than asserted by authors". The tooling existed and lived in a workspace harness, so it answered for whoever ran it and for no one else. A rule set that reaches no fixed point is a hang rather than a wrong answer, and a hang is what a test suite reports worst — it aborts the run instead of failing a test.

What it checks

Two questions, because they have different answers.

every set settles except
iterated alone Power, NumericNeat
with the normalisation between passes — how Simplify runs them Common

Power splits a power of a product — (2 * x) ^ 2 to 2 ^ 2 * x ^ 2 — and something has to fold 2 ^ 2 before the result stops being a power of a product. NumericNeat rewrites --x through a product of ones that only collapses when the normalisation multiplies them out. Both are the same shape: a right-hand side that is a fixed point only after arithmetic the set itself does not do.

Both lists are asserted in both directions

A set that starts cycling fails, and a set that stops cycling fails too. A list that outlives what it describes is exactly how the workspace reports went stale, and the sets are named rather than counted — a count going from two to two says nothing when one set was fixed and another started.

What it found

Common has a genuine three-cycle on -x * 1/2, filed as #1056:

Mulf(-1/2, x)  ->  Mulf(-1, Divf(x, 2))  ->  Divf(Mulf(-1, x), 2)  ->  Mulf(-1/2, x)

Three trees printing as two strings, which is why the test records shapes and not printed forms. Two rules disagree about whether c * x or (c * x) / d is the destination.

Simplify bounds its own iteration and does not hang — "-x * 1/2".Simplify() is -1/2 * x — so nothing a caller sees today is broken by it. It is recorded here rather than fixed: choosing an orientation for a set that runs on nearly every simplification is a decision, not a patch.

The corpus

Leaves, then unary and binary shapes over them, then a third level that is load-bearing — the shapes that cycle are a unary applied to something already compound ((2 * x) ^ 2, --x), and without it the second assertion fires on an empty list. That is what it did the first time it ran.

Cost

2 s for both tests. The corpus is parsed once into a static; building it per rule set parsed the same few hundred strings sixty times over, which was 83 s of the run and none of its coverage.

Full suite green: Failed: 0, Passed: 8576, Skipped: 14, Total: 8590.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd

#746 tier 2 asks for
termination "checked by tooling rather than asserted by authors". The tooling
existed -- work/rulecheck -- and lived in a workspace, so it answered for
whoever ran it and for no one else. A rule set that reaches no fixed point is a
hang rather than a wrong answer, and a hang is what a test suite reports worst.

Two facts, because they have different answers. Iterated alone, every set in
the registry settles except Power and NumericNeat, both of which want the
normalisation to fold arithmetic their own right-hand sides leave behind. With
the normalisation between passes -- how Simplify actually runs them -- every
set settles except Common.

Both lists are asserted in both directions, so a set that starts cycling fails
and a set that stops cycling fails too: a list that outlives what it describes
is how the workspace reports went stale. The sets are named rather than
counted, because a count that goes from two to two says nothing when one set
was fixed and another started.

Common's failure is a genuine three-cycle on -x * 1/2 -- Mulf(-1/2, x),
Mulf(-1, Divf(x, 2)), Divf(Mulf(-1, x), 2) -- three trees printing as two
strings, which is why it is written down as shapes. Two rules disagree about
whether c * x or (c * x) / d is the destination. Simplify bounds its own
iteration and does not hang, so nothing a caller sees today is broken by it;
it is filed as #1056 and
recorded here rather than fixed, because choosing an orientation for a set that
runs on nearly every simplification is a decision and not a patch.

The corpus goes three levels deep, and the third level is load-bearing: the
shapes that cycle are a unary applied to something already compound, and
without it the second assertion fires on an empty list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd
@Rafael-SOWNet
Rafael-SOWNet merged commit b6ed3d5 into master Aug 25, 2026
27 checks passed
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.

2 participants