> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Check messages: older raters and placeholders

> These messages name a form from an earlier RSL version, or a part of a rater that is still a placeholder.

*Generated from the `rslc` compiler.*

## E-LOOKUP-TOTAL

**State a lookup's default in the table.**

`E-LOOKUP-TOTAL` guards against a lookup that carries a default value of its own. RSL source spells a lookup one way, `lookup!(Table, key, …)`, which takes keys alone, so `rslc check` leaves this code unprinted.

A table states the row for all other keys with `otherwise [cite: …]: { … }`. A lookup that can reach a key with a missing row gets `E-LOOKUP-GAP`.

## E-ENTITY-REL

**Give each record one enclosing record.**

A record that contains another is its parent, and the contained record is its child. The check asks that each link runs both ways: the child of a record names that record as its parent.

The compiler derives both directions from the field types, so a rater written in current forms keeps them in step. The message appears when a second problem leaves a record with two candidate parents, and it arrives together with the message for that problem. Fix that one and both clear.

The words `[children]` and `[parent]` in the message are the annotations of an earlier version of RSL. Read them as "contains" and "is contained by".

**Example**

`Policy` and `Branch` each contain a `Location`, so `Location` has two enclosing records.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Branch = {
        inputs {
            location: Location
        }
    },
    Location = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-RECORD-POSITION bad/rater.rsl:33:13: record type `Location` is used at two field positions, `Policy.head_office` and `Branch.location`, so `Location`'s enclosing record and its instance count are ambiguous (E-RECORD-POSITION). Give each position its own record type, such as `Location` and `BranchLocation`; the two can share field types
error: E-ENTITY-REL bad/rater.rsl:31:5: Branch declares [children] Location but Location has no [parent] back-reference
```

**Fix**

Give the second position its own record type, as `E-RECORD-POSITION` advises. The two types share the same fields.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Branch = {
        inputs {
            location: BranchLocation
        }
    },
    Location = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        }
    },
    BranchLocation = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
```

**Other causes**

An older rater marks a field `[parent]` and gives it a type other than the enclosing record, such as `policy: Option(Policy) [parent]`. That rater gets `E-RELATION-REMOVED` as well: delete the field and read the enclosing record by its name.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             head_office: Location,
>             branch: Branch
>         },
>         step rating {
>             payroll: Num = self.head_office.payroll + self.branch.location.payroll,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Branch = {
>         inputs {
>             location: Location
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             head_office: Location,
>             branch: Branch
>         },
>         step rating {
>             payroll: Num = self.head_office.payroll + self.branch.location.payroll,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Branch = {
>         inputs {
>             location: BranchLocation
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     },
>     BranchLocation = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-AXIS-SUBSET

**Give each map key one declaration.**

A map holds one value for each member of its enum, such as a rate for each coverage. The check asks that exactly one declaration states the value of each key.

Earlier versions of RSL built a map from several declarations, each over part of the keys. Two of them could claim the same key, and this message names that key. RSL 0.4 gives a map one declaration, so the message always arrives with `E-AXES-REMOVED` for the second declaration.

One declaration that chooses with `match c` shows every key's value in one place, and the compiler checks that each key has an arm.

**Example**

The map `rate` is declared for every `Coverage` and again for `FirstParty`, so two declarations claim `property`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        liability,
        property
    },
    FirstParty = Coverage where self in {@Coverage.property}
}
...
        },
        step rating {
            rate[c: Coverage]: Rate = liability_rate,
            rate[c: FirstParty]: Rate = property_rate,
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-AXES-REMOVED bad/rater.rsl:36:13: map `rate` is declared more than once. Per-subset declarations are a removed form (E-AXES-REMOVED): write one declaration over the union of the keys and specialise per key with `match c { @E.a => …, @E.b => … }`
error: E-AXIS-SUBSET bad/rater.rsl:36:13: attribute `rate` has overlapping or ambiguous subsets for axis element `property` (exactly one authored owner is required)
```

**Fix**

Write one declaration over the enum and give each key its value in a `match c` arm. Delete the refined enum once it has become unused.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        liability,
        property
    }
}
...
        },
        step rating {
            rate[c: Coverage]: Rate =
                match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
```

**Other causes**

The two declarations name different enums, such as `rate[c: Coverage]` and `rate[p: Peril]`. That rater gets `E-MERGE-FIDELITY` as well. Give the second map its own name.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     },
>     FirstParty = Coverage where self in {@Coverage.property}
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate = liability_rate,
>             rate[c: FirstParty]: Rate = property_rate,
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(Coverage as c, self.coverage_premium[c])
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     }
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate =
>                 match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(Coverage as c, self.coverage_premium[c])
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-HOLE

**Replace a placeholder with a calculation.**

A placeholder, `nl "…"`, holds a calculation that is still written in plain words. `E-HOLE` is the code for a value that depends on a placeholder. `rslc check` reports the placeholder itself, on a line that starts `obligation: 25`, and leaves `E-HOLE` unprinted.

A placeholder lets you sketch a rater and fill it in step by step. `rslc check --allow-incomplete` accepts a rater whose only open findings are placeholders. The strict check fails until each one is RSL, and `rslc export` refuses a rater whose outcomes depend on one.

**Example**

The calculation `premium` is a placeholder that describes the rule in words.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = nl "payroll per 1,000 times the filed rate, rounded to the cent"
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
obligation: 25 bad/rater.rsl:26:50: Unresolved typed hole Hole:0 (payroll per 1,000 times the filed rate, rounded to the cent)
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Replace the placeholder with the calculation it describes.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = nl "payroll per 1,000 times the filed rate, rounded to the cent"
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-ROUND-INC

**Round to a scale and a mode.**

`E-ROUND-INC` is reserved for a rounding increment that is malformed, as in the removed form `round(x, 0.01)`. `rslc check` reports every rounding problem as `E-ROUND`, and that message gives the current form: `round(x, 2, half_up)`.

## E-TABLE-MODE

**Declare a table with dims and bands.**

Earlier versions of RSL declared a table as `( … ) -> { … }` with a `match:` line. A range table matched a number against a pair of minimum and maximum columns. RSL 0.4 has one table form: `table dims (key: Type, …) -> { outputs }`.

In that form each key cell is exact, and a range is a band type declared in `Types`. `IntBands(0, 250000, 1000000..)` lists the lower cut of each band, and the CSV holds one row for each cut.

With the cuts in the type, the compiler proves that every payroll the rater accepts falls in one band.

**Example**

The table `PayrollRate` uses the removed signature, `NumRange` and `match: range` over a minimum and a maximum column.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Tables = {
    PayrollRate [cite: manual:p4] :
        (payroll: NumRange) -> {
            rate: Rate
        }
        match: range
        columns: {
            payroll: ["min_payroll", "max_payroll"]
        }
}
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
min_payroll,max_payroll,rate
0,249999,2.4
250000,999999,2.1
1000000,100000000,1.8
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-TABLE-MODE bad/rater.rsl:17:5: table `PayrollRate` uses `match: range` with a breakpoint column, a removed form. Write `PayrollRate: table dims (key: DimType, …) -> { outputs }`: every key cell is exact (no `_`), ranges are IntBands/DecimalBands dimensions, and `otherwise [cite: source:region]: { … }` (the filed all-other row) covers tuples that no row names. Rewrite an older rater in RSL 0.4 with this rslc
```

**Fix**

Declare the bands in `Types` and key the table with them. Delete the `match:` and `columns:` lines, and keep one CSV column that holds each band's lower cut.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Types = {
    PayrollBand = IntBands(0, 250000, 1000000..)
}

Tables = {
    PayrollRate [cite: manual:p4] :
        table dims (payroll: PayrollBand) -> {
            rate: Rate
        }
}
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
payroll,rate
0,2.4
250000,2.1
1000000,1.8
```

**Other causes**

* A `table dims` table still has a `match:` line. Delete the line.
* A table has the `( … ) -> { … }` signature with `match: specific`, or with the `match:` line left out. Write `table dims` before the keys.
* An input or a table key has the type `NumRange`. Declare an `IntBands` or `DecimalBands` type and use it.
* A `columns:` entry maps a key to a pair of headers. Map one header that holds the lower cut.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Tables = {
>     PayrollRate [cite: manual:p4] :
>         (payroll: NumRange) -> {
>             rate: Rate
>         }
>         match: range
>         columns: {
>             payroll: ["min_payroll", "max_payroll"]
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate: Rate = lookup!(PayrollRate, self.payroll).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/PayrollRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> min_payroll,max_payroll,rate
> 0,249999,2.4
> 250000,999999,2.1
> 1000000,100000000,1.8
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     PayrollBand = IntBands(0, 250000, 1000000..)
> }
>
> Tables = {
>     PayrollRate [cite: manual:p4] :
>         table dims (payroll: PayrollBand) -> {
>             rate: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate: Rate = lookup!(PayrollRate, self.payroll).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/PayrollRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> payroll,rate
> 0,2.4
> 250000,2.1
> 1000000,1.8
> ```

## E-REQUIRES

**Keep exact constraint forms in constraints.**

An input constraint decides which requests the rater accepts, so the compiler evaluates each one in exact arithmetic. Two forms exist for constraints alone, and this message reports one of them out of place or malformed.

* `N% of x` is an exact ratio. It belongs in a refinement, such as `where self.subcontracted <= 50% of self.payroll`.
* `values: Csv` on a numeric type keeps the type's permitted values in `enums/<Type>.csv`, under the header `value`.

A calculation states its arithmetic with ordinary operators and its rounding with `round`.

**Example**

The calculation `premium` uses the exact ratio `50% of self.payroll`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(50% of self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-REQUIRES bad/rater.rsl:26:56: an exact ratio `N% of x` is only permitted in an input constraint, such as a record refinement
```

**Fix**

Write the ratio as a multiplication.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(0.5 * self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

**Other causes**

Each of these concerns a numeric type with `values: Csv`, such as `Limit = Num {precision: int, values: Csv}`.

* The rater's folder lacks an `enums/` folder, or the folder lacks `Limit.csv`. Add the file.
* The file is empty, or its header is something other than `value`. Start the file with the line `value`.
* The file has a header and zero values. List at least one value.
* A cell holds text where a number belongs, or repeats an earlier value. Keep one row for each number.
* A value has more decimal places than the type's `precision`. Match the values to the precision.
* A value lies outside the range in the type's `where self in […]`. Widen the range or remove the value.
* The type leaves out `precision`. State one, such as `precision: int`.
* `values:` names something other than `Csv`. Write `values: Csv`.
* The braces state `values:` twice. Keep one.
* An input wraps the type, as `Option(Limit)` or `List(Limit)`. Use the type directly on a single input.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(50% of self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(0.5 * self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-REJECT

**A reject needs a code and message.**

`reject("CODE", "message")` is a form from earlier versions of RSL, where a calculation refused a request. The check reads its two arguments as text literals. The code starts with a capital letter and continues with capital letters, digits, `_` and `-`. The message holds at least one character.

RSL 0.4 removes `reject` itself, so this message always arrives with a second one for the removed form. In a table's `otherwise: reject(…)` that is `E-OTHERWISE-REJECT-REMOVED`, and inside `checked(…)` it is `E-CHECKED-REMOVED`. Fix both at once by stating the rule as a refinement.

**Example**

The calculation `premium` calls `reject` with a code in lower case.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                if self.payroll < 1000 then reject("min-payroll", "Payroll must be at least 1,000.")
                else round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-REJECT bad/rater.rsl:27:52: reject code `min-payroll` must match [A-Z][A-Z0-9_-]*
error: E-REJECT-REMOVED bad/rater.rsl:27:45: a calculation cannot reject: `reject("min-payroll", …)` in `Policy.premium` is a removed form. This is an input-domain rule (rejection census class A1)
  = because: calculations are total: admission is the only place a rating can fail, so a rule that refuses a request is a refinement on the inputs it reads
  = help: delete the rejecting branch and state the rule as a refinement of `Policy`: `Policy = { … } where not (self.payroll < 1000) [code: "min-payroll", message: "Payroll must be at least 1,000."]` (add `cite: <source:region>` to the annotation). Rewrite an older rater in RSL 0.4 with this rslc
  = applicability: placeholders
```

**Fix**

Delete the rejecting branch and state the rule as a refinement on the input, with a code in capitals.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int}
                where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

**Other causes**

* The call has one argument, or three: `reject` takes a code and a message.
* An argument is a number or a name where the call needs a text literal.
* The code or the message is the empty text `""`.
* A table's `otherwise: reject(…)` has a malformed code or an empty message.
* The third argument of `checked(x, T, …)` is something other than a `reject` call.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 if self.payroll < 1000 then reject("min-payroll", "Payroll must be at least 1,000.")
>                 else round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int}
>                 where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-MERGE-FIDELITY

**Declare each map once, over one enum.**

Earlier versions of RSL built a map from several declarations and merged them. This check confirms that the merged map gives each key the value its own declaration wrote. It reports a key whose owner is ambiguous after the merge.

RSL 0.4 gives a map one declaration, so the merge has a single input and the check holds. The message appears when a name is declared as a map twice, over two different enums. It arrives with `E-AXES-REMOVED` for the second declaration and `E-AXIS-SUBSET` for the two enums.

The numbers in the message are positions in the file and the compiler's own labels. Read the map's name and the key, then find the two declarations.

**Example**

The step declares `rate` over `Coverage` and again over `Peril`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        liability,
        property
    },
    Peril = {
        fire,
        wind
    }
}
...
            rate[c: Coverage]: Rate =
                match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
            rate[p: Peril]: Rate = property_rate,
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-AXES-REMOVED bad/rater.rsl:40:13: map `rate` is declared more than once. Per-subset declarations are a removed form (E-AXES-REMOVED): write one declaration over the union of the keys and specialise per key with `match c { @E.a => …, @E.b => … }`
error: E-AXIS-SUBSET bad/rater.rsl:40:13: attribute `rate` has declarations on different resolved axes
error: E-MERGE-FIDELITY bad/rater.rsl:38:13: attribute `rate` axis element `liability` did not preserve its owning declaration at [715..847]: expected owner body Node:10, found ambiguous ownership
error: E-MERGE-FIDELITY bad/rater.rsl:38:13: attribute `rate` axis element `property` did not preserve its owning declaration at [715..847]: expected owner body Node:10, found ambiguous ownership
```

**Fix**

Keep one declaration of `rate`, over one enum. A second map, over another enum, takes its own name.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        liability,
        property
    }
}
...
            rate[c: Coverage]: Rate =
                match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     },
>     Peril = {
>         fire,
>         wind
>     }
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate =
>                 match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
>             rate[p: Peril]: Rate = property_rate,
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(Coverage as c, self.coverage_premium[c])
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     }
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate =
>                 match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(Coverage as c, self.coverage_premium[c])
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-BAND-COVERAGE

**Cover every lookup key with a band.**

`E-BAND-COVERAGE` is retired. It was the code for a number that falls outside every band of the band type that keys a table. `rslc check` reports that problem at the lookup as `E-KEY-RANGE`, with the range the key can take and the range the bands cover.

## E-EXTERN-REMOVED

**Declare an external table as an ordinary table.**

Earlier versions of RSL let a table point at rows held outside the rater, with `[extern: "…"]` and related annotations. RSL 0.4 keeps every table's rows in the rater's own `tables/` folder, where the check reads them.

With the rows in the folder, the compiler proves that each lookup finds a row. A large table needs the same declaration as a small one, and the compiler chooses how to store it.

A lookup by an identifier, such as a class code, keys the table with a `Code` type. An `otherwise` row gives the filed rate for every code the table leaves out.

**Example**

The table `ClassRate` carries the removed `extern` annotation and has its rows outside the rater.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Tables = {
    ClassRate [cite: manual:p4, extern: "class_rates"] :
        table dims (class_code: ClassCode) -> {
            rate: Rate
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-EXTERN-REMOVED bad/rater.rsl:21:5: table `ClassRate` uses the removed `extern` annotation. Externs are removed. Declare an ordinary table with a Code key and put its rows in tables/ClassRate.csv: `ClassRate: table dims (key: KeyCode) -> { … }` with `KeyCode = Code(pattern: "…")`. Narrow the key input to the filed rows, or write the filed all-other row as `otherwise [cite: …]: { … }`. Rewrite an older rater in RSL 0.4 with this rslc
```

**Fix**

Delete the annotation and save the rows as `tables/ClassRate.csv`, with one header for each key and each column.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Tables = {
    ClassRate [cite: manual:p4] :
        table dims (class_code: ClassCode) -> {
            rate: Rate
```

```csv tables/ClassRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
class_code,rate
7361,1.2
7363,2.4
8810,0.6
```

**Other causes**

The annotations `implementation`, `path`, `sqlite_table` and `reject` on a table also belonged to external tables and get the same message. Delete each one.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     ClassCode = Code(pattern: "[0-9]{4}")
> }
>
> Tables = {
>     ClassRate [cite: manual:p4, extern: "class_rates"] :
>         table dims (class_code: ClassCode) -> {
>             rate: Rate
>         }
>         otherwise [cite: manual:p5]: { rate: 3.1 }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             class_code: ClassCode
>         },
>         step rating {
>             rate: Rate = lookup!(ClassRate, self.class_code).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     ClassCode = Code(pattern: "[0-9]{4}")
> }
>
> Tables = {
>     ClassRate [cite: manual:p4] :
>         table dims (class_code: ClassCode) -> {
>             rate: Rate
>         }
>         otherwise [cite: manual:p5]: { rate: 3.1 }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             class_code: ClassCode
>         },
>         step rating {
>             rate: Rate = lookup!(ClassRate, self.class_code).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/ClassRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> class_code,rate
> 7361,1.2
> 7363,2.4
> 8810,0.6
> ```

## E-TEXT-REMOVED

**Replace Text with a code or an enum.**

Earlier versions of RSL had a `Text` type for free text. In RSL 0.4 the type of every value states what the value accepts, so the compiler can check each rule that reads it.

An identifier, such as a class code or a ZIP code, is a `Code` type with a pattern: `ClassCode = Code(pattern: "[0-9]{4}")`. A closed set of labels is an enum. A value that rating leaves unread, such as the insured's name, belongs to the system that calls the rater.

A `Text` input that lacks the `[extern]` annotation gets `E-TYPE` as well. One fix clears both messages.

**Example**

The input `insured_name` has the removed type `Text`, and the premium ignores it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            insured_name: Text [extern]
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-TEXT-REMOVED bad/rater.rsl:24:27: `Text` is removed: declare an identifier as a `Code(pattern: "…")` type in `Types` and a closed set of labels as an enum. A value that no rating rule reads does not belong in the rater; delete it. Rewrite an older rater in RSL 0.4 with this rslc
```

**Fix**

Delete the input: the premium reads only `payroll`.

Where a rule reads the value, declare its type in `Types` as a `Code` with a pattern, or as an enum of its labels.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             insured_name: Text [extern]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-AXIS-INDEX

**Read a map at a key it defines.**

`E-AXIS-INDEX` is the code for a read of an indexed value at an element it leaves undefined. An indexed value in RSL 0.4 is a map over an enum, and `rslc check` reports the same problem as `E-MAP-DOMAIN`.

## E-REQUIRES-REMOVED

**Write a record rule as a refinement.**

A rule that compares two inputs of one record belongs to the record. Earlier versions of RSL wrote such rules in a `requires` block inside the record. RSL 0.4 writes each rule as a refinement after the record's closing brace: `} where condition`.

A refinement states which requests the rater accepts. The compiler uses it when it proves each calculation, and a request that breaks the rule is refused before rating starts.

The message quotes the first clause of the block as the refinement to write.

**Example**

The record `Policy` states its rule in a `requires` block.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            subcontracted: Num {precision: int} where self in [0, 100000000]
        },
        requires {
            self.subcontracted <= self.payroll
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
}
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-REQUIRES-REMOVED bad/rater.rsl:26:9: `requires { … }` is a removed form: a rule over a record's inputs is a refinement of the record. Delete the block and write each clause after the record's closing brace: `} where self.subcontracted <= self.payroll`
```

**Fix**

Delete the block and write its clause after the record's closing brace. A block with several clauses becomes one `where` for each clause, written one after another.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            subcontracted: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    } where self.subcontracted <= self.payroll
}
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             subcontracted: Num {precision: int} where self in [0, 100000000]
>         },
>         requires {
>             self.subcontracted <= self.payroll
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             subcontracted: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     } where self.subcontracted <= self.payroll
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-RANGE-REMOVED

**Write a value range as a refinement.**

Earlier versions of RSL stated the values a number accepts inside the type's braces: `{precision: int, range: [0, 100]}`. RSL 0.4 keeps `precision` in the braces and writes the range as a refinement after the type: `where self in [0, 100]`.

A refinement is the one place a rater states which values it accepts. The compiler reads it to prove each calculation, and a request outside the range is refused before rating starts.

The message gives the refinement to write, with the bounds copied from the old `range`.

**Example**

The input `payroll` states its range as `range:` metadata.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int, range: [0, 100000000]}
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-RANGE-REMOVED bad/rater.rsl:23:26: `range:` metadata is a removed form: a value range is a refinement; precision stays in the type. Write `where self in [0, 100000000]` after the type and its annotations
```

**Fix**

Delete `range:` from the braces and write the bounds as `where self in […]` after the type. Annotations in brackets, such as a label, stay before the `where`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
```

**Other causes**

* A type alias in `Types` states `range:` metadata. Write the same refinement after the alias's type.
* A table column states `range:` metadata. Delete it: the rows of the table give the column's values.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int, range: [0, 100000000]}
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-AXES-REMOVED

**Replace an axis with an enum and maps.**

Earlier versions of RSL declared coverages, perils and similar dimensions in an `Axes` block and indexed values by axis. RSL 0.4 uses the forms it has for every other set of labels:

* The dimension is an enum in `Types`, such as `Coverage`.
* A value for each member is a map with one declaration: `rate[c: Coverage]`.
* A value that differs by member chooses with `match c`.
* A read names one key: `self.rate[c]` or `self.rate[@Coverage.liability]`.
* A total names the enum and a binder: `sum(Coverage as c, self.rate[c])`.

A subset of the members is a refined enum, declared in `Types` and used in the same places.

With one declaration for each map, the compiler proves that every key has a value.

**Example**

The total `premium` folds over an axis with the removed form `sum(x over axis)`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
            premium: Premium = sum(self.coverage_premium[c] over coverage)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-AXES-REMOVED bad/rater.rsl:38:32: `sum(x over axis)` is a removed form (E-AXES-REMOVED): a fold over an enum names its binder and reads the map at it, `sum(Coverage as c, self.premium[c])`; a refined enum folds over a subset, `sum(WindPeril as p, self.premium[p])`
```

**Fix**

Name the enum and a binder first, then read the map at the binder.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * self.rate[c], 2, half_up),
            premium: Premium = sum(Coverage as c, self.coverage_premium[c])
        }
    }
```

**Other causes**

* The file has an `Axes` block. Declare each axis as an enum in `Types`, as `Coverage` is in the example.
* A step declares one member of a map, as `rate[liability]`, or a listed subset. Write one declaration over the enum and choose with `match c`.
* A step declares the same map twice. Write one declaration over all the keys.
* A read lists several keys, as `self.rate[c: {liability, property}]`. Read one key.
* A table column lists members, as `rate: Rate[{liability, property}]`. Key the column by the enum: `rate: Rate[Coverage]`.
* An input carries an axis annotation, as `limit: Num [coverage]`. Declare one input for each member and build the map in a step.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     }
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate =
>                 match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(self.coverage_premium[c] over coverage)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         liability,
>         property
>     }
> }
>
> Constants = {
>     liability_rate [cite: manual:p3] = 2,
>     property_rate [cite: manual:p3] = 0.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rate[c: Coverage]: Rate =
>                 match c { @Coverage.liability => liability_rate, @Coverage.property => property_rate },
>             coverage_premium[c: Coverage]: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[c], 2, half_up),
>             premium: Premium = sum(Coverage as c, self.coverage_premium[c])
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-RELATION-REMOVED

**Declare records and contain them by type.**

Earlier versions of RSL declared `Entities` and linked them with annotated fields: `[children]` on the parent and a `[parent]` field on the child. RSL 0.4 declares `Records`, and a record contains another by naming its type.

* A field typed `Location` holds one record.
* A field typed `Option(Location)` holds at most one.
* A field typed `List(Location)` holds several.

A contained record reads its ancestor by the record's name, as `Policy.experience_mod`. The compiler knows each record's place from the types, so the back-reference field has gone.

**Example**

The file declares `Entities`, a `[children]` list of `EntityId(Location)` and a `[parent]` field on `Location`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Entities = {
    Policy = {
        inputs {
            experience_mod: Factor {precision: 0.01} where self in [0.5, 2],
            locations: List(EntityId(Location)) [children] where len(self) in [1, 50]
        },
        step rating {
...
    Location = {
        inputs {
            policy: EntityId(Policy) [parent],
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.policy.experience_mod, 2, half_up)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-RELATION-REMOVED @339-1124: `Entities` is a removed form (E-RELATION-REMOVED): declare `Records = { … }`. A relation is a field typed `R`, `Option(R)` or `List(R)`; delete `[parent]` fields and read an ancestor as `Policy.x`. Rewrite an older rater in RSL 0.4 with this rslc
error: E-RELATION-REMOVED bad/rater.rsl:24:13: relation annotations are a removed form (E-RELATION-REMOVED): write `locations: List(R)`, `locations: R` or `locations: Option(R)` without `[children]`
error: E-RELATION-REMOVED bad/rater.rsl:32:13: relation annotations are a removed form (E-RELATION-REMOVED): delete the `[parent]` field `policy`; read the enclosing record as `Policy.x` (its record type name)
```

**Fix**

Rename the section to `Records` and type the list `List(Location)`. Delete the `[parent]` field and read the policy's value as `Policy.experience_mod`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Records = {
    Policy = {
        inputs {
            experience_mod: Factor {precision: 0.01} where self in [0.5, 2],
            locations: List(Location) where len(self) in [1, 50]
        },
        step rating {
...
    Location = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * Policy.experience_mod, 2, half_up)
        }
    }
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Entities = {
>     Policy = {
>         inputs {
>             experience_mod: Factor {precision: 0.01} where self in [0.5, 2],
>             locations: List(EntityId(Location)) [children] where len(self) in [1, 50]
>         },
>         step rating {
>             premium: Premium [cite: manual:p5] = round(sum(self.locations as l, l.premium), 0, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             policy: EntityId(Policy) [parent],
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.policy.experience_mod, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             experience_mod: Factor {precision: 0.01} where self in [0.5, 2],
>             locations: List(Location) where len(self) in [1, 50]
>         },
>         step rating {
>             premium: Premium [cite: manual:p5] = round(sum(self.locations as l, l.premium), 0, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * Policy.experience_mod, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-REJECT-REMOVED

**State a refusal as an input refinement.**

Earlier versions of RSL let a calculation refuse a request with `reject("CODE", "message")`. In RSL 0.4 every calculation returns a value for every request the rater accepts. A rule that refuses a request is a refinement on the inputs it reads.

The refinement keeps the business code and the message. A request that breaks the rule gets that code before rating starts, and the calculation holds only arithmetic.

The message names the calculation and the kind of rule it found. Its help line writes the refinement, with the code and message copied from the `reject`.

**Example**

The calculation `premium` refuses a payroll below 1,000 with `reject`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                if self.payroll < 1000 then reject("MIN-PAYROLL", "Payroll must be at least 1,000.")
                else round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-REJECT-REMOVED bad/rater.rsl:27:45: a calculation cannot reject: `reject("MIN-PAYROLL", …)` in `Policy.premium` is a removed form. This is an input-domain rule (rejection census class A1)
  = because: calculations are total: admission is the only place a rating can fail, so a rule that refuses a request is a refinement on the inputs it reads
  = help: delete the rejecting branch and state the rule as a refinement of `Policy`: `Policy = { … } where not (self.payroll < 1000) [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000."]` (add `cite: <source:region>` to the annotation). Rewrite an older rater in RSL 0.4 with this rslc
  = applicability: placeholders
```

**Fix**

Delete the rejecting branch. State the rule where the input is declared, as a range with the code, the message and a citation.

The help line shows a second form: a refinement of the whole record, written after its closing brace. Use that form for a rule that reads several inputs.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int}
                where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 if self.payroll < 1000 then reject("MIN-PAYROLL", "Payroll must be at least 1,000.")
>                 else round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int}
>                 where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-OTHERWISE-REJECT-REMOVED

**Refuse an unrated key at the input.**

Earlier versions of RSL let a table refuse a key that lacks a row, with `otherwise: reject("CODE", "message")`. In RSL 0.4 a lookup returns a row for every request the rater accepts. A rule that refuses a request is a refinement on the input it reads.

The refinement keeps the business code. A request with an unrated key gets that code before rating starts, and the compiler proves that each lookup finds a row.

`otherwise` remains for the filed all-other row, written with values and a citation: `otherwise [cite: manual:p4]: { rate: 3.1 }`.

**Example**

The table `TerritoryRate` files rates for `urban` and `rural`, and refuses `offshore` with `otherwise: reject`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            rate: Rate
        }
        otherwise: reject("NO-RATE", "This territory has no filed rate.")
}
...
            payroll: Num {precision: int} where self in [0, 100000000],
            territory: Territory
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-OTHERWISE-REJECT-REMOVED bad/rater.rsl:29:9: a table cannot reject; each `lookup!` must be total at its call site: `otherwise: reject("NO-RATE", …)` of table `TerritoryRate` is a removed form
  = because: calculations are total: admission is the only place a rating can fail, so a rule that refuses a request is a refinement on the inputs it reads
  = help: delete the clause. If a lookup can still reach a key tuple with no row, that is a table gap (census A5): narrow the key type to the filed rows (a refined enum `E - { … }`, or an identity enum of the filed key tuples) with the business code on the input, `where … [code: "NO-RATE", message: "…", cite: …]`; mark filed holes `unfiled`; or, when the filing has an all-other row, write it as `otherwise [cite: …]: { … }`. Rewrite an older rater in RSL 0.4 with this rslc
  = applicability: manual
```

**Fix**

Delete the clause and narrow the input that the lookup reads, with the code, the message and a citation.

Where the filing prints a rate for all other keys, write that row as `otherwise [cite: …]: { … }`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            rate: Rate
        }
}
...
            payroll: Num {precision: int} where self in [0, 100000000],
            territory: Territory
                where self != @Territory.offshore [code: "NO-RATE", message: "This territory has no filed rate.", cite: manual:p4]
        },
        step rating {
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Territory = {
>         urban,
>         rural,
>         offshore
>     }
> }
>
> Tables = {
>     TerritoryRate [cite: manual:p4] :
>         table dims (territory: Territory) -> {
>             rate: Rate
>         }
>         otherwise: reject("NO-RATE", "This territory has no filed rate.")
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             territory: Territory
>         },
>         step rating {
>             rate: Rate = lookup!(TerritoryRate, self.territory).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/TerritoryRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate
> urban,2.4
> rural,1.9
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Territory = {
>         urban,
>         rural,
>         offshore
>     }
> }
>
> Tables = {
>     TerritoryRate [cite: manual:p4] :
>         table dims (territory: Territory) -> {
>             rate: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             territory: Territory
>                 where self != @Territory.offshore [code: "NO-RATE", message: "This territory has no filed rate.", cite: manual:p4]
>         },
>         step rating {
>             rate: Rate = lookup!(TerritoryRate, self.territory).rate,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/TerritoryRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate
> urban,2.4
> rural,1.9
> ```

## E-CHECKED-REMOVED

**Prove a range from the inputs.**

Earlier versions of RSL tested a value against a type while rating: `checked(x, T, reject("CODE", "message"))` refused a request whose `x` fell outside `T`. In RSL 0.4 every calculation returns a value for every request the rater accepts. The compiler proves that `x` lies in `T` from the ranges of the inputs.

A rule that refuses a request is a refinement on the input it reads. The refinement keeps the business code, and a request outside the range gets that code before rating starts.

**Example**

The calculation `rated_payroll` tests the payroll against the type `RatedPayroll` with `checked`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating {
            rated_payroll: RatedPayroll [cite: manual:p2] =
                checked(self.payroll, RatedPayroll, reject("MIN-PAYROLL", "Payroll must be at least 1,000.")),
            premium: Premium [cite: manual:p3] = round(self.rated_payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-CHECKED-REMOVED bad/rater.rsl:31:17: a calculation cannot reject, so `T` must be proved, not checked: `checked(x, T, reject(…))` is a removed form
  = because: calculations are total: admission is the only place a rating can fail, so a rule that refuses a request is a refinement on the inputs it reads
  = help: narrow the inputs `x` reads with refinements so the compiler proves `x` is in `T` (keep the business code on the input: `where … [code: "…", message: "…", cite: …]`), write the filed clamp `max(min(x, hi), lo)`, or delete the declared range. When `x` is a computed key between filed cuts, write the filed rounding in a cited step or declare the band over the unrounded key (see E-KEY-HOLE). Rewrite an older rater in RSL 0.4 with this rslc
  = applicability: manual
```

**Fix**

Narrow the input to the range of the type, with the code, the message and a citation. Then read the input directly: the compiler proves the range.

Where the filing caps the value, write the cap as arithmetic, such as `max(min(self.payroll, 100000000), 1000)`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: Num {precision: int}
                where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
        },
        step rating {
            rated_payroll: RatedPayroll [cite: manual:p2] = self.payroll,
            premium: Premium [cite: manual:p3] = round(self.rated_payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     RatedPayroll = Num {precision: int} where self in [1000, 100000000]
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             rated_payroll: RatedPayroll [cite: manual:p2] =
>                 checked(self.payroll, RatedPayroll, reject("MIN-PAYROLL", "Payroll must be at least 1,000.")),
>             premium: Premium [cite: manual:p3] = round(self.rated_payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     RatedPayroll = Num {precision: int} where self in [1000, 100000000]
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int}
>                 where self in [1000, 100000000] [code: "MIN-PAYROLL", message: "Payroll must be at least 1,000.", cite: manual:p2]
>         },
>         step rating {
>             rated_payroll: RatedPayroll [cite: manual:p2] = self.payroll,
>             premium: Premium [cite: manual:p3] = round(self.rated_payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-SLOT-REMOVED

**Let the formula state the rule.**

Earlier versions of RSL let the brackets of a calculation repeat what its formula does, in keys such as `at_least:`, `total:` and `chain:`. The compiler reads the same facts from the formula, so RSL 0.4 writes each rule once.

One statement of a rule stays true when the formula changes. A key beside the formula could say one thing while the arithmetic did another.

The message names the key, and its help line says which part of the formula carries the meaning.

**Example**

The calculation `premium` applies a minimum with `max(…)` and repeats it as `at_least: minimum_premium`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3, at_least: minimum_premium] =
                max(round(self.payroll / 1000 * rate_per_1000, 2, half_up), minimum_premium)
        }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-SLOT-REMOVED bad/rater.rsl:27:48: `at_least:` on `Policy.premium` restates the program, so it was removed
  = because: a rating rule is spelled once, in the program
  = help: remove `at_least: …`: `max(…)` in the formula says it
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Delete the key. The `max(…)` in the formula states the minimum.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                max(round(self.payroll / 1000 * rate_per_1000, 2, half_up), minimum_premium)
        }
```

**Other causes**

Eleven keys get this message. Delete each one; the list says what states the rule in its place.

* `at_least:` is stated by `max(…)` in the formula.
* `at_most:` is stated by `min(…)` in the formula.
* `total:` is stated by the sum in the formula.
* `product:` and `chain:` are stated by the product in the formula.
* `by:` is stated by the keys of the lookup.
* `selects:` is stated by the inputs the calculation reads.
* `series:` is stated by a map value that reads its own previous element.
* `bands:` becomes a group: write `collect` and a `title`.
* `when:` becomes a condition: write `exception when …` or `with … otherwise`.
* `within:` becomes a choice: write `select(low..high, position)`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     minimum_premium [cite: manual:p4] = 500
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3, at_least: minimum_premium] =
>                 max(round(self.payroll / 1000 * rate_per_1000, 2, half_up), minimum_premium)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     minimum_premium [cite: manual:p4] = 500
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 max(round(self.payroll / 1000 * rate_per_1000, 2, half_up), minimum_premium)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-INVARIANT

**Meet each structural rule of a rater.**

`E-INVARIANT` is reserved for a rater that breaks one of the compiler's numbered structural rules. `rslc check` prints such a finding on a line of its own. The line starts with the kind of finding and the rule's number, in place of an `E-` code.

A placeholder, `nl "…"`, is one such case: its line starts `obligation: 25`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.