# Rules and the transform contract

The rule metadata model, fixed-point scheduling, the eliminated-forms contract, and how to read rule source.

https://texform.dev/docs/rule-contract

Read this if you want to contribute a rewrite rule or audit why the transform engine produces a given output. For the user-facing view of profiles and the phase pipeline, start with [Concepts: Transforms](https://texform.dev/docs/transforms).

## The rule metadata model [#the-rule-metadata-model]

Normalization is not find-and-replace. Each rewrite rule declares the metadata the engine uses to schedule rules, verify convergence, and enforce its output contract:

* **`key`** — a stable `package/name` identifier used in transform reports and build-time filters.
* **`enabled_by_packages`** — the packages whose presence makes the rule loadable.
* **`level`** — the first profile that accepts the rule output: `Authoring`, `Faithful`, `Corpus`, or `Equiv`.
* **`summary`** — a one-line description.
* **`fidelity`** — the worst-case equivalence guarantee over the declared input domain: `Math`, `Reading`, or `Render`.
* **`triggers`** — the targets the scheduler watches.
* **`consumes`** — forms the rule eliminates or may touch.
* **`produces`** — forms the rule may introduce.

### Level and fidelity are orthogonal [#level-and-fidelity-are-orthogonal]

A rule's level classifies the intended product, not the size of its visual change:

| Level       | Intent                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `Authoring` | Author-editable canonical syntax that preserves reasonable notation and style choices.                                       |
| `Faithful`  | Render-faithful universal forms for compact, package-specific, or legacy input.                                              |
| `Corpus`    | Complete, stable canonical forms suitable as training labels; reading-identical layout and vocabulary variants may collapse. |
| `Equiv`     | Comparison-only intermediates that need not remain suitable as source or training labels.                                    |

Fidelity is a separate worst-case guarantee ordered `Math < Reading < Render`. `Render` preserves rendering under the reference renderer, `Reading` preserves notation content, reading order, and structural roles while allowing layout changes, and `Math` preserves mathematical meaning over the declared domain while allowing notation and rendering to change. Authoring, Faithful, and Corpus require at least Reading; Equiv requires at least Math. A stronger fidelity does not automatically move a rule to an earlier level.

Because the levels are cumulative, the Equiv profile runs every Corpus rule and then the `Equiv` rules on top. Those rules drop distinctions that two spellings of the same mathematics do not need to agree on: `\cfrac{a}{b}` becomes `\frac{a}{b}`, `\widehat` collapses onto `\hat`, explicit `\big` / `\Big` delimiter sizing is dropped, and fixed horizontal spacing such as `\quad` disappears. That is exactly why they are comparison-only — `\cfrac` was chosen by an author for a reason, and an Equiv output is not a faithful replacement for the source.

## Fixed-point scheduling [#fixed-point-scheduling]

Rewrite is one phase in a fixed sequence — pre-rewrite LowerAttributes, Rewrite, post-rewrite LowerAttributes, FinalizeAst, FlattenGroups, then FinalizeAst a second time. The second FinalizeAst pass is the same idempotent pass, re-run because flattening can bring nodes next to each other that were not adjacent before; it is skipped when FlattenGroups is disabled, since nothing new can become adjacent. FinalizeAst is the last phase that mutates the tree, and the eliminated-forms check that follows is read-only.

When a transform context is built, the engine filters all registered rules through the selected profile's levels, build-time filters, and enabled packages. At run time it repeatedly applies that precompiled plan until a full pass changes nothing or `max_iterations` is reached.

The transform report exposes this directly: `rewrite.iterations` is the fixed-point iteration count, including the final unchanged check, and `rewrite.rules` lists each recorded rule with its `key`, `applied_count`, and `skipped_count`. Plain `normalize` returns only the string. Request the report with `normalize_with_report` in Python or `normalizeWithReport` in JavaScript.

## The eliminated-forms contract [#the-eliminated-forms-contract]

A rule that removes a form declares it in `consumes.eliminates`. After the complete pipeline has run, the engine checks that no declared eliminated form remains. A violation is a transform error, not a warning or a half-normalized result.

The `texform-regression` corpus harness runs the same contract over real-world data. Known, reasoned exceptions live in `regression/contract_exceptions.yaml`; a new unlisted violation means the declared contract and actual behavior no longer agree.

## What callers can change [#what-callers-can-change]

The rule set is compiled into the engine, so there is no run-time registration hook: you cannot add a rule of your own without building TeXForm from source. What you can do from the outside is subtract. Pass `disable_rules` with the keys you want left out when you construct the engine — `disable_rules=["physics/dv-to-frac-d"]` in Python, `disableRules` in JavaScript — and the plan is compiled without them. [Disable a rule](https://texform.dev/docs/disable-a-rule) walks through finding the key in a transform report and turning it off.

## How to read rule source [#how-to-read-rule-source]

Rules live under [`src/rewrite/rules/`](https://github.com/texform-dev/texform/tree/main/crates/texform-transform/src/rewrite/rules) using `<package>/<level>/<group>/<rule>.rs`. The level directory is one of `authoring`, `faithful`, `corpus`, or `equiv`; the build script verifies it agrees with `RuleMeta.level`. Rule keys remain `package/name` regardless of the source path.

Most rules use `define_rule!`, which keeps metadata beside the rewrite logic:

```rust
define_rule! {
    pub static OVER_TO_FRAC: OverToFracRule {
        key: Base / "over-to-frac",
        level: Authoring,
        summary: "Rewrite infix over to an explicit frac command.",
        fidelity: Render,
        enabled_by_packages: [Base],
        triggers: cmd_targets![&base::cmd::OVER],
        consumes: RuleConsumes {
            eliminates: cmd_targets![&base::cmd::OVER],
            touches: &[],
        },
        produces: RuleProduces {
            targets: cmd_targets![&base::cmd::FRAC],
        },
        apply(rule, cx, node_id) {
            // match the \over infix and rebuild it as \frac{left}{right}
        }
    }
}
```

This rule eliminates `\over`, produces `\frac`, first enters at Authoring, and guarantees Render fidelity. Other useful examples include alias rules such as `physics/derivative-to-dv`, which eliminates `\derivative` and produces `\dv`, and Corpus cleanup rules such as `base/goodbreak-drop`. The former `displaylines-to-gather-env` proposal was rejected and removed because it could not guarantee Reading over a reasonable declared domain.

## Source map [#source-map]

Paths are relative to the [TeXForm repository](https://github.com/texform-dev/texform) root. They move as the code moves, so treat them as entry points rather than citations.

| Path                                                   | What it defines                                                                              |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `crates/texform-transform/src/engine.rs`               | The fixed phase order, including the second FinalizeAst pass and the eliminated-forms check. |
| `crates/texform-transform/README.md`                   | Phase behavior, profile-to-level mapping, and the fidelity definitions.                      |
| `crates/texform-transform/src/rewrite/rules/README.md` | The rule authoring guide and the metadata contract.                                          |
| `crates/texform-transform/src/rewrite/rules/`          | Every built-in rule, laid out as `<package>/<level>/<group>/<rule>.rs`.                      |
| `regression/contract_exceptions.yaml`                  | The reasoned exceptions to the eliminated-forms contract over corpus data.                   |

## Related [#related]

- [Concepts: Transforms](https://texform.dev/docs/transforms) — Profiles, the phase pipeline, and transform reports.

- [Disable a rewrite rule](https://texform.dev/docs/disable-a-rule) — Find a rule key in a report and switch that rule off.

- [Internals: Architecture](https://texform.dev/docs/architecture) — Where the transform engine sits among the crates.

- [Rule source on GitHub](https://github.com/texform-dev/texform/tree/main/crates/texform-transform/src/rewrite/rules) — The complete rule tree and authoring guide.

