> ## 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: names, types and records

> These messages concern names, types, records and the values that pass between them.

*Generated from the `rslc` compiler.*

## E-UNRESOLVED

**Every name matches a declaration.**

A rater reads each value by name: an input, a calculation, a constant, a table, a type, an enum member or a function. The check asks every name to match a declaration the compiler has read.

A misspelt name stops the check at its line and column. The rater then reads only values that exist, and a typing slip surfaces before any request is rated.

The same code reports a file the compiler fails to read as a whole, such as a syntax error or a missing import.

**Example**

The premium reads `rate_per_100`, and the constant is named `rate_per_1000`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_100, 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-UNRESOLVED bad/rater.rsl:26:78: unresolved name 'rate_per_100'
```

**Fix**

Spell the name as its declaration spells it.

```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)
        }
    }
```

**Other causes**

* A misspelt input or calculation after `self.` gets `unknown attribute 'payrol' on current entity`.
* A misspelt function gets a message that names it and lists the functions RSL has.
* A misspelt type gets `unknown type 'Mony'`.
* A lookup of a table that `Tables` lacks gets `unknown table 'BaseRate'`.
* A misspelt enum or member gets `unknown enum 'Teir'` or `unknown member 'prefered' in enum 'Tier'`.
* A `Ref` to a name that `Records` lacks gets `` `Ref(Driver)` names no record type ``.
* An outcome that names a missing calculation gets `` rating outcome `premium` references unknown attribute `Policy.premum` ``.
* A syntax error gets `parse error in …: expected value`, with the place where reading stopped.
* An `import` of a missing file gets `cannot resolve import "other.rsl"`, with the folders searched.
* A name declared twice in one section gets `duplicate Constants declaration 'rate_per_1000'`.

> [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(self.payroll / 1000 * rate_per_100, 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-DATE

**A date literal names a real calendar day.**

A date in a rater is written `YYYY-MM-DD`. The check asks each date literal in a calculation to name a day the calendar has.

A date such as `2026-06-31` reads like a date and names a day that June lacks. The check reports it where it is written, so a comparison or a date difference always works from a real day.

**Example**

The rate changes on `2026-06-31`, and June has 30 days.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            rate: Rate = if self.effective >= 2026-06-31 then surcharge_rate_per_1000 else rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 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-DATE bad/rater.rsl:28:47: invalid calendar date `2026-06-31`
```

**Fix**

Write the day the filing means. Here the higher rate starts on 30 June.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            rate: Rate = if self.effective >= 2026-06-30 then surcharge_rate_per_1000 else rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 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,
>     surcharge_rate_per_1000 [cite: manual:p4] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             effective: Date where self in [2026-01-01, 2026-12-31]
>         },
>         step rating {
>             rate: Rate = if self.effective >= 2026-06-31 then surcharge_rate_per_1000 else rate_per_1000,
>             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
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     surcharge_rate_per_1000 [cite: manual:p4] = 3
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             effective: Date where self in [2026-01-01, 2026-12-31]
>         },
>         step rating {
>             rate: Rate = if self.effective >= 2026-06-30 then surcharge_rate_per_1000 else rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-PATTERN

**A match arm uses a pattern RSL knows.**

A `match` chooses a result by the value of its subject. Each arm starts with a pattern, and the pattern follows the subject's type.

An enum subject takes `@Enum.member` arms. A `Bool` takes `true` and `false`, an `Option` takes `None` and `Some(x)`, and a union takes one arm per record type. `_` matches every remaining value of an enum, `Bool` or `Option`.

The check asks each arm to use one of these patterns, of the kind its subject takes. A pattern of another kind would match zero requests, so the compiler reports it instead of skipping the arm.

**Example**

The first arm names the enum member as the string `"standard"`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            tier_factor: Factor [cite: manual:p4] =
                match self.tier {
                               "standard" => 1,
                               @Tier.preferred => 0.9
                           },
```

```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-PATTERN bad/rater.rsl:36:32: unrecognized match pattern; legal patterns are `_`, `@Enum.member`, `true`, `false`, `None`, and `Some(...)`
```

**Fix**

Name the member with its enum, as `@Tier.standard`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            tier_factor: Factor [cite: manual:p4] =
                match self.tier {
                               @Tier.standard => 1,
                               @Tier.preferred => 0.9
                           },
```

**Other causes**

* `None` or `Some(x)` on a subject of another type gets `` `None` and `Some(…)` match an `Option` ``, with the subject's type.
* A capitalised arm such as `Standard(s)` on an enum gets ``arm pattern `Standard` matches a union input``.
* `_` or `@Enum.member` on a union gets `a union is matched arm by arm, by record type`.
* A union arm that names another record type gets `` `Unrted` is not an arm of this union ``, with the union's arms.

> [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 = {
>     Tier = {
>         standard,
>         preferred
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             tier: Tier
>         },
>         step rating {
>             tier_factor: Factor [cite: manual:p4] =
>                 match self.tier {
>                                "standard" => 1,
>                                @Tier.preferred => 0.9
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.tier_factor, 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 = {
>     Tier = {
>         standard,
>         preferred
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             tier: Tier
>         },
>         step rating {
>             tier_factor: Factor [cite: manual:p4] =
>                 match self.tier {
>                                @Tier.standard => 1,
>                                @Tier.preferred => 0.9
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.tier_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-TYPE

**Each value has the type its place expects.**

Every value in a rater has a type: a number, a `Bool`, a `Date`, an enum member, a record or a list. Each place that takes a value states the type it takes, such as the condition of an `if` or a key of a lookup.

The check asks the two to agree. A number where a `Bool` belongs, or an enum member in arithmetic, is a mistake in the formula. The compiler reports it before any request is rated.

RSL converts between types only where you write the conversion. A condition is a comparison such as `self.payroll > 0`, and an enum becomes a factor through a `match` or a table.

**Example**

The condition of the `if` is the number `self.payroll`, and an `if` takes a `Bool`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            earned: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
            premium: Premium = if self.payroll then self.earned else deposit_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-TYPE bad/rater.rsl:28:35: if condition expects Bool, got Num
```

**Fix**

Write the condition as a comparison.

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

**Other causes**

* A calculation whose value differs from its declared type gets ``attribute `premium` expects Premium, got Bool``.
* An enum member in arithmetic gets `arithmetic operand expects Num`.
* A comparison of an enum with a number gets `this comparison compares enum … with Num`.
* An ordering such as `>` on an enum gets `only numbers and dates are ordered`.
* A lookup with more or fewer keys than the table has dimensions gets `pass one key per dim, in order`.
* A lookup key of another type than its dimension gets `lookup argument 1 of … takes a number`.
* A read through a list, such as `self.items.charge`, gets `is a list of instances`; bind the list in a fold such as `sum`.
* A read through a field that holds a plain value, such as `self.payroll.amount`, gets `is not an entity navigation field`.
* A `[key]` after a value that is a single number gets ``attribute `item_charge` is not indexed``.
* `None` as the value of a calculation of a plain type gets `` `None` requires expected type Option(T) ``.
* An enum that lists one member twice gets `declares member … more than once`.

> [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,
>     deposit_premium [cite: manual:p4] = 250
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             earned: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium = if self.payroll then self.earned else deposit_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,
>     deposit_premium [cite: manual:p4] = 250
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             earned: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium = if self.payroll > 0 then self.earned else deposit_premium
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-DATA-AS-CODE

**A long list of values needs a file.**

A rater's source holds rating logic, and its data lives in CSV files beside it. The check asks a list of more than 256 values to move out of the source and into a file.

The limit applies to three forms: the members of an enum written in `Types`, the values of a membership test such as `self in {…}`, and the arms of a `match`.

A list this long is data the filing publishes as a table or an appendix. In a CSV file it loads fast, a reviewer compares it with the filing row by row, and the rater stays short enough to read.

**Example**

The input `class_code` lists 257 eligible class codes inside the rater.

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

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
...
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            class_code: Num {precision: int}
                where self in [8001, 8897]
                where self in {
                    8001, 8005, 8008, 8012, 8015, 8019, 8022, 8026, 8029, 8033, 8036, 8040,
                    8043, 8047, 8050, 8054, 8057, 8061, 8064, 8068, 8071, 8075, 8078, 8082,
                    8085, 8089, 8092, 8096, 8099, 8103, 8106, 8110, 8113, 8117, 8120, 8124,
                    8127, 8131, 8134, 8138, 8141, 8145, 8148, 8152, 8155, 8159, 8162, 8166,
                    8169, 8173, 8176, 8180, 8183, 8187, 8190, 8194, 8197, 8201, 8204, 8208,
                    8211, 8215, 8218, 8222, 8225, 8229, 8232, 8236, 8239, 8243, 8246, 8250,
                    8253, 8257, 8260, 8264, 8267, 8271, 8274, 8278, 8281, 8285, 8288, 8292,
                    8295, 8299, 8302, 8306, 8309, 8313, 8316, 8320, 8323, 8327, 8330, 8334,
                    8337, 8341, 8344, 8348, 8351, 8355, 8358, 8362, 8365, 8369, 8372, 8376,
                    8379, 8383, 8386, 8390, 8393, 8397, 8400, 8404, 8407, 8411, 8414, 8418,
                    8421, 8425, 8428, 8432, 8435, 8439, 8442, 8446, 8449, 8453, 8456, 8460,
                    8463, 8467, 8470, 8474, 8477, 8481, 8484, 8488, 8491, 8495, 8498, 8502,
                    8505, 8509, 8512, 8516, 8519, 8523, 8526, 8530, 8533, 8537, 8540, 8544,
                    8547, 8551, 8554, 8558, 8561, 8565, 8568, 8572, 8575, 8579, 8582, 8586,
                    8589, 8593, 8596, 8600, 8603, 8607, 8610, 8614, 8617, 8621, 8624, 8628,
                    8631, 8635, 8638, 8642, 8645, 8649, 8652, 8656, 8659, 8663, 8666, 8670,
                    8673, 8677, 8680, 8684, 8687, 8691, 8694, 8698, 8701, 8705, 8708, 8712,
                    8715, 8719, 8722, 8726, 8729, 8733, 8736, 8740, 8743, 8747, 8750, 8754,
                    8757, 8761, 8764, 8768, 8771, 8775, 8778, 8782, 8785, 8789, 8792, 8796,
                    8799, 8803, 8806, 8810, 8813, 8817, 8820, 8824, 8827, 8831, 8834, 8838,
                    8841, 8845, 8848, 8852, 8855, 8859, 8862, 8866, 8869, 8873, 8876, 8880,
                    8883, 8887, 8890, 8894, 8897
                }
        },
        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-DATA-AS-CODE bad/rater.rsl:26:23: inline membership has 257 values; the limit is 256 because larger domains are captured data, not rating logic. Declare the field domain as a CSV-backed enum (`TypeName = Enum` with `enums/TypeName.csv`) or a CSV-backed numeric type (`values: Csv`); use a table and `lookup!` when values map to results
```

**Fix**

Declare a number type with `values: Csv` and list the values in `enums/ClassCode.csv`, under a `value` column. The file takes the name of the type.

```csv enums/ClassCode.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
value
8001
8005
8008
8012
8015
8019
8022
8026
8029
8033
8036
8040
8043
8047
8050
8054
8057
8061
8064
8068
8071
8075
8078
8082
8085
8089
8092
8096
8099
8103
8106
8110
8113
8117
8120
8124
8127
8131
8134
8138
8141
8145
8148
8152
8155
8159
8162
8166
8169
8173
8176
8180
8183
8187
8190
8194
8197
8201
8204
8208
8211
8215
8218
8222
8225
8229
8232
8236
8239
8243
8246
8250
8253
8257
8260
8264
8267
8271
8274
8278
8281
8285
8288
8292
8295
8299
8302
8306
8309
8313
8316
8320
8323
8327
8330
8334
8337
8341
8344
8348
8351
8355
8358
8362
8365
8369
8372
8376
8379
8383
8386
8390
8393
8397
8400
8404
8407
8411
8414
8418
8421
8425
8428
8432
8435
8439
8442
8446
8449
8453
8456
8460
8463
8467
8470
8474
8477
8481
8484
8488
8491
8495
8498
8502
8505
8509
8512
8516
8519
8523
8526
8530
8533
8537
8540
8544
8547
8551
8554
8558
8561
8565
8568
8572
8575
8579
8582
8586
8589
8593
8596
8600
8603
8607
8610
8614
8617
8621
8624
8628
8631
8635
8638
8642
8645
8649
8652
8656
8659
8663
8666
8670
8673
8677
8680
8684
8687
8691
8694
8698
8701
8705
8708
8712
8715
8719
8722
8726
8729
8733
8736
8740
8743
8747
8750
8754
8757
8761
8764
8768
8771
8775
8778
8782
8785
8789
8792
8796
8799
8803
8806
8810
8813
8817
8820
8824
8827
8831
8834
8838
8841
8845
8848
8852
8855
8859
8862
8866
8869
8873
8876
8880
8883
8887
8890
8894
8897
```

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

Types = {
    ClassCode = Num {precision: int, values: Csv}
}

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
...
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            class_code: ClassCode where self in [8001, 8897]
        },
        step rating {
```

**Other causes**

* An enum with more than 256 members gets ``inline enum `Territory` has 257 members``. Declare `Territory = Enum` and list the members in `enums/Territory.csv`, or type an identifier such as a ZIP code as `Code(pattern: "…")`.
* A `match` with more than 256 arms gets `match has 257 literal value arms`. Move the rows to a table and read the result with `lookup!`.

> [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],
>             class_code: Num {precision: int}
>                 where self in [8001, 8897]
>                 where self in {
>                     8001, 8005, 8008, 8012, 8015, 8019, 8022, 8026, 8029, 8033, 8036, 8040,
>                     8043, 8047, 8050, 8054, 8057, 8061, 8064, 8068, 8071, 8075, 8078, 8082,
>                     8085, 8089, 8092, 8096, 8099, 8103, 8106, 8110, 8113, 8117, 8120, 8124,
>                     8127, 8131, 8134, 8138, 8141, 8145, 8148, 8152, 8155, 8159, 8162, 8166,
>                     8169, 8173, 8176, 8180, 8183, 8187, 8190, 8194, 8197, 8201, 8204, 8208,
>                     8211, 8215, 8218, 8222, 8225, 8229, 8232, 8236, 8239, 8243, 8246, 8250,
>                     8253, 8257, 8260, 8264, 8267, 8271, 8274, 8278, 8281, 8285, 8288, 8292,
>                     8295, 8299, 8302, 8306, 8309, 8313, 8316, 8320, 8323, 8327, 8330, 8334,
>                     8337, 8341, 8344, 8348, 8351, 8355, 8358, 8362, 8365, 8369, 8372, 8376,
>                     8379, 8383, 8386, 8390, 8393, 8397, 8400, 8404, 8407, 8411, 8414, 8418,
>                     8421, 8425, 8428, 8432, 8435, 8439, 8442, 8446, 8449, 8453, 8456, 8460,
>                     8463, 8467, 8470, 8474, 8477, 8481, 8484, 8488, 8491, 8495, 8498, 8502,
>                     8505, 8509, 8512, 8516, 8519, 8523, 8526, 8530, 8533, 8537, 8540, 8544,
>                     8547, 8551, 8554, 8558, 8561, 8565, 8568, 8572, 8575, 8579, 8582, 8586,
>                     8589, 8593, 8596, 8600, 8603, 8607, 8610, 8614, 8617, 8621, 8624, 8628,
>                     8631, 8635, 8638, 8642, 8645, 8649, 8652, 8656, 8659, 8663, 8666, 8670,
>                     8673, 8677, 8680, 8684, 8687, 8691, 8694, 8698, 8701, 8705, 8708, 8712,
>                     8715, 8719, 8722, 8726, 8729, 8733, 8736, 8740, 8743, 8747, 8750, 8754,
>                     8757, 8761, 8764, 8768, 8771, 8775, 8778, 8782, 8785, 8789, 8792, 8796,
>                     8799, 8803, 8806, 8810, 8813, 8817, 8820, 8824, 8827, 8831, 8834, 8838,
>                     8841, 8845, 8848, 8852, 8855, 8859, 8862, 8866, 8869, 8873, 8876, 8880,
>                     8883, 8887, 8890, 8894, 8897
>                 }
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/enums/ClassCode.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> value
> 8001
> 8005
> 8008
> 8012
> 8015
> 8019
> 8022
> 8026
> 8029
> 8033
> 8036
> 8040
> 8043
> 8047
> 8050
> 8054
> 8057
> 8061
> 8064
> 8068
> 8071
> 8075
> 8078
> 8082
> 8085
> 8089
> 8092
> 8096
> 8099
> 8103
> 8106
> 8110
> 8113
> 8117
> 8120
> 8124
> 8127
> 8131
> 8134
> 8138
> 8141
> 8145
> 8148
> 8152
> 8155
> 8159
> 8162
> 8166
> 8169
> 8173
> 8176
> 8180
> 8183
> 8187
> 8190
> 8194
> 8197
> 8201
> 8204
> 8208
> 8211
> 8215
> 8218
> 8222
> 8225
> 8229
> 8232
> 8236
> 8239
> 8243
> 8246
> 8250
> 8253
> 8257
> 8260
> 8264
> 8267
> 8271
> 8274
> 8278
> 8281
> 8285
> 8288
> 8292
> 8295
> 8299
> 8302
> 8306
> 8309
> 8313
> 8316
> 8320
> 8323
> 8327
> 8330
> 8334
> 8337
> 8341
> 8344
> 8348
> 8351
> 8355
> 8358
> 8362
> 8365
> 8369
> 8372
> 8376
> 8379
> 8383
> 8386
> 8390
> 8393
> 8397
> 8400
> 8404
> 8407
> 8411
> 8414
> 8418
> 8421
> 8425
> 8428
> 8432
> 8435
> 8439
> 8442
> 8446
> 8449
> 8453
> 8456
> 8460
> 8463
> 8467
> 8470
> 8474
> 8477
> 8481
> 8484
> 8488
> 8491
> 8495
> 8498
> 8502
> 8505
> 8509
> 8512
> 8516
> 8519
> 8523
> 8526
> 8530
> 8533
> 8537
> 8540
> 8544
> 8547
> 8551
> 8554
> 8558
> 8561
> 8565
> 8568
> 8572
> 8575
> 8579
> 8582
> 8586
> 8589
> 8593
> 8596
> 8600
> 8603
> 8607
> 8610
> 8614
> 8617
> 8621
> 8624
> 8628
> 8631
> 8635
> 8638
> 8642
> 8645
> 8649
> 8652
> 8656
> 8659
> 8663
> 8666
> 8670
> 8673
> 8677
> 8680
> 8684
> 8687
> 8691
> 8694
> 8698
> 8701
> 8705
> 8708
> 8712
> 8715
> 8719
> 8722
> 8726
> 8729
> 8733
> 8736
> 8740
> 8743
> 8747
> 8750
> 8754
> 8757
> 8761
> 8764
> 8768
> 8771
> 8775
> 8778
> 8782
> 8785
> 8789
> 8792
> 8796
> 8799
> 8803
> 8806
> 8810
> 8813
> 8817
> 8820
> 8824
> 8827
> 8831
> 8834
> 8838
> 8841
> 8845
> 8848
> 8852
> 8855
> 8859
> 8862
> 8866
> 8869
> 8873
> 8876
> 8880
> 8883
> 8887
> 8890
> 8894
> 8897
> ```
>
> ```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 = Num {precision: int, values: Csv}
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             class_code: ClassCode where self in [8001, 8897]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-SELECT-TIE

**A selection states how a tie breaks.**

`argmax`, `argmin`, `topn`, `find` and `rank` pick records from a list. Two records can tie: two locations with the same payroll both have the greatest score.

The check asks the rater to state which record wins a tie. The list's key decides: declare the list `keyed by` an input that names each record once, and the lowest key wins.

One request then rates the same way however its list is ordered. Where the filing ranks tied records by a rule of its own, write that rule first, as `then [cite: …]: priority`.

**Example**

`argmax` picks the location with the greatest payroll, and the list `locations` lacks a key.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            locations: List(Location) where len(self) in [1, 20]
        },
        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-SELECT-TIE bad/rater.rsl:27:43: `argmax` over a list of `Location` breaks a tie by the list's key, and this list has none, so the order of the request would decide. Key the list field it reads: `<list>: List(Location) keyed by <field>`, where `<field>` is an input of `Location` that names each element once (a number, Date, enum member or code; admission refuses a repeated key). A filed priority goes first: `argmax(…, then [cite: <source>:<region>]: <priority>)`
```

**Fix**

Key the list by the input that numbers each location.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            locations: List(Location) keyed by number where len(self) in [1, 20]
        },
        step rating {
```

**Other causes**

* A tie priority written as a plain last argument gets `` a tie priority is written `then` ``. Write `then [cite: manual:p3]: l.hazard_factor`.
* A `then` priority on `find` gets a message that names `argmax`, `argmin`, `topn` and `rank` as the functions that take 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
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = sum(self.locations as l, l.payroll),
>             governing: Option(Location) = argmax(self.locations as l, l.payroll),
>             hazard_factor: Factor =
>                 match self.governing {
>                                Some(g) => g.hazard_factor,
>                                None => 1
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             number: Num {precision: int} where self in [1, 999],
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard_factor: Num {precision: 0.01} where self in [0.5, 3]
>         }
>     }
> }
>
> 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 {
>             locations: List(Location) keyed by number where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = sum(self.locations as l, l.payroll),
>             governing: Option(Location) = argmax(self.locations as l, l.payroll),
>             hazard_factor: Factor =
>                 match self.governing {
>                                Some(g) => g.hazard_factor,
>                                None => 1
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             number: Num {precision: int} where self in [1, 999],
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard_factor: Num {precision: 0.01} where self in [0.5, 3]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-KEYED-ORDER

**A keyed list is unordered.**

A list declared `keyed by` is a map from key to record. The key names each item, and the order of the request is free to change.

`index(self)` and `index(v)` read the position of an item in its list. The check refuses them on a keyed list, so one request rates the same way however its list is ordered.

Where a filing orders the items, state that order in the rater: as an input of the item, or with `rank` over a rating value.

**Example**

`position_factor` reads the position of a vehicle, and the list `vehicles` is keyed by `number`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            position_factor: Factor = if index(self) = 0 then 1 else additional_vehicle_factor,
            premium: Premium [cite: manual:p3] =
                round(self.value / 1000 * rate_per_1000 * self.position_factor, 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-KEYED-ORDER bad/rater.rsl:36:42: `vehicles` is keyed by `number`, so it has no order. Fix: add an order input to the item (such as `rank`), or use `rank(…)`
```

**Fix**

Rank the vehicles by value, with the key as the tie break, and read the rank. The vehicle of the greatest value takes rank 0.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            value_rank: Num = rank(Policy.vehicles as v, v.value, self.number, desc),
            position_factor: Factor = if self.value_rank = 0 then 1 else additional_vehicle_factor,
            premium: Premium [cite: manual:p3] =
                round(self.value / 1000 * rate_per_1000 * self.position_factor, 2, half_up)
```

**Other causes**

* `=` between two lists of keyed records gets `` `=` compares lists by position ``. Compare the items by key with `all` or `exists`, or compare the counts.

> [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: "Fleet Auto",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 12,
>     additional_vehicle_factor [cite: manual:p4] = 0.9
> }
>
> Records = {
>     Policy = {
>         inputs {
>             vehicles: List(Vehicle) keyed by number where len(self) in [1, 4]
>         },
>         step rating {
>             premium: Premium [cite: manual:p5] = round(sum(self.vehicles as v, v.premium), 0, half_up)
>         }
>     },
>     Vehicle = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             value: Num {precision: int} where self in [1000, 200000]
>         },
>         step rating {
>             position_factor: Factor = if index(self) = 0 then 1 else additional_vehicle_factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.value / 1000 * rate_per_1000 * self.position_factor, 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: "Fleet Auto",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 12,
>     additional_vehicle_factor [cite: manual:p4] = 0.9
> }
>
> Records = {
>     Policy = {
>         inputs {
>             vehicles: List(Vehicle) keyed by number where len(self) in [1, 4]
>         },
>         step rating {
>             premium: Premium [cite: manual:p5] = round(sum(self.vehicles as v, v.premium), 0, half_up)
>         }
>     },
>     Vehicle = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             value: Num {precision: int} where self in [1000, 200000]
>         },
>         step rating {
>             value_rank: Num = rank(Policy.vehicles as v, v.value, self.number, desc),
>             position_factor: Factor = if self.value_rank = 0 then 1 else additional_vehicle_factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.value / 1000 * rate_per_1000 * self.position_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-UNION

**A union arm holds inputs only.**

A union input such as `experience: Rated | Unrated` accepts one of several record types. Each request sends one arm, named by its `tag`, with that arm's fields.

The check asks each arm to be a record type that holds inputs and their refinements, each input a single value. The record that owns the union computes from the arm, inside a `match` that names every arm.

Every calculation that depends on the arm then sits in one place, and the `match` shows what each arm contributes to the premium.

**Example**

The arm `Rated` computes `capped_modifier` in a step of its own.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            experience_factor: Factor [cite: manual:p4] =
                match self.experience {
                               Rated(r) => r.modifier,
                               Unrated => 1
                           },
...
        inputs {
            modifier: Num {precision: 0.01} where self in [0.5, 2]
        },
        step rating {
            capped_modifier: Factor = min(self.modifier, 1.5)
        }
    },
```

```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-UNION bad/rater.rsl:36:5: `Rated` is a union arm, so it holds inputs and refinements only; compute from it in the owner: `match self.x { Rated(a) => … a.field …, … }`
```

**Fix**

Move the calculation into the owner's `match`, in the arm that binds `Rated`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            experience_factor: Factor [cite: manual:p4] =
                match self.experience {
                               Rated(r) => min(r.modifier, 1.5),
                               Unrated => 1
                           },
...
        inputs {
            modifier: Num {precision: 0.01} where self in [0.5, 2]
        }
    },
```

**Other causes**

* A refinement on the union field gets ``union `experience` takes no metadata, annotations or refinements``. Refine the arm record types.
* A union that lists one arm twice gets ``union `experience` names arm `Rated` twice``.
* An arm that `Records` lacks gets ``union arm `Unrate` must be a record type``.
* An arm field named `tag` gets `` `tag` names the arm in the request ``. Give the field another name.
* An arm field that holds a list or a record gets ``arm field `years` must be a number, Bool, Date, enum or Code, or an `Option` of 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
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             experience: Rated | Unrated
>         },
>         step rating {
>             experience_factor: Factor [cite: manual:p4] =
>                 match self.experience {
>                                Rated(r) => r.modifier,
>                                Unrated => 1
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
>         }
>     },
>     Rated = {
>         inputs {
>             modifier: Num {precision: 0.01} where self in [0.5, 2]
>         },
>         step rating {
>             capped_modifier: Factor = min(self.modifier, 1.5)
>         }
>     },
>     Unrated = {
>     }
> }
>
> 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],
>             experience: Rated | Unrated
>         },
>         step rating {
>             experience_factor: Factor [cite: manual:p4] =
>                 match self.experience {
>                                Rated(r) => min(r.modifier, 1.5),
>                                Unrated => 1
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
>         }
>     },
>     Rated = {
>         inputs {
>             modifier: Num {precision: 0.01} where self in [0.5, 2]
>         }
>     },
>     Unrated = {
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-UNION-USE

**A union is read through a match.**

A union input such as `experience: Rated | Unrated` holds one of several record types. Each arm has its own fields, so a field such as `modifier` exists only when the request sends the `Rated` arm.

The check asks every read of a union to be a `match` that names each arm. Inside an arm such as `Rated(r)`, the name `r` reads that arm's fields.

Each field is then read only where it exists, and a new arm added later is reported by every `match` that omits it.

**Example**

The factor compares the union with an arm name in an `if`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            experience_factor: Factor [cite: manual:p4] = if self.experience = Unrated then 1 else 0.9,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 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-UNION-USE bad/rater.rsl:27:62: union `experience` is read only by `match`: `match self.experience { Arm(x) => … x.field …, … }`
```

**Fix**

Read the union with a `match`. The `Rated` arm binds its record as `r` and reads the modifier from it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            experience_factor: Factor [cite: manual:p4] =
                match self.experience {
                               Rated(r) => r.modifier,
                               Unrated => 1
                           },
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
```

**Other causes**

* An arm that reads a field its record lacks gets `` `r.modifer`: arm `Rated` has no such field; its fields are `modifier` ``.
* A record refinement that reads an arm field as `self.experience.modifier` gets the same message for each arm that lacks the field.
* A `[key]` after an arm field gets ``variant field `r.modifier` is not indexed``.

> [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],
>             experience: Rated | Unrated
>         },
>         step rating {
>             experience_factor: Factor [cite: manual:p4] = if self.experience = Unrated then 1 else 0.9,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
>         }
>     },
>     Rated = {
>         inputs {
>             modifier: Num {precision: 0.01} where self in [0.5, 2]
>         }
>     },
>     Unrated = {
>     }
> }
>
> 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],
>             experience: Rated | Unrated
>         },
>         step rating {
>             experience_factor: Factor [cite: manual:p4] =
>                 match self.experience {
>                                Rated(r) => r.modifier,
>                                Unrated => 1
>                            },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
>         }
>     },
>     Rated = {
>         inputs {
>             modifier: Num {precision: 0.01} where self in [0.5, 2]
>         }
>     },
>     Unrated = {
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-RECORD-ROOT

**A rater has one top record.**

The records of a rater form one tree. A field typed with a record type, such as `worksites: List(Worksite)`, places that record under the record that declares the field.

The check asks for one record type that sits at the top, under which every other record hangs. That top record is the request: one policy, with its worksites inside it.

One tree gives every value one place in a request. A record outside the tree stays empty in every request, so its inputs and calculations would be dead.

**Example**

The rater declares `Worksite` and leaves it outside `Policy`, so both sit at the top.

```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] = 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-RECORD-ROOT bad/rater.rsl:29:5: the program has 2 root record types (Policy, Worksite); the request is the one record type that no field contains (E-RECORD-ROOT). Delete an unread root, or nest it under the real root as a field, such as `worksite: Worksite`
```

**Fix**

Nest the record under the top record as a field, and read it from there. Delete a record that the rater has stopped using.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            worksites: List(Worksite) where len(self) in [1, 20]
        },
        step rating {
            payroll: Num = sum(self.worksites as w, w.payroll),
            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] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Worksite = {
>         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 {
>             worksites: List(Worksite) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = sum(self.worksites as w, w.payroll),
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Worksite = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-RECORD-POSITION

**Each record type sits at one field.**

A field typed with a record type places that record in the request tree. `worksites: List(Worksite)` puts every `Worksite` inside a `Policy`, in that list.

The check asks each record type to be the type of one field. A record type then has one enclosing record and one path in a request, such as `Policy.worksites[]`.

A calculation in the record relies on that place. It reads its enclosing record by type name, and the compiler counts its instances from the one field that holds it.

**Example**

`Worksite` types two fields of `Policy`: `headquarters` and `worksites`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            headquarters: Worksite,
            worksites: List(Worksite) where len(self) in [1, 20]
        },
...
            payroll: Num = self.headquarters.payroll + sum(self.worksites as w, w.payroll),
            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-RECORD-POSITION bad/rater.rsl:24:13: record type `Worksite` is used at two field positions, `Policy.headquarters` and `Policy.worksites`, so `Worksite`'s enclosing record and its instance count are ambiguous (E-RECORD-POSITION). Give each position its own record type, such as `Worksite` and `PolicyWorksite`; the two can share field types
```

**Fix**

Declare a record type for each field. Two record types that hold the same inputs share a type alias from `Types` for each input.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            headquarters: Headquarters,
            worksites: List(Worksite) where len(self) in [1, 20]
        },
...
            payroll: Num = self.headquarters.payroll + sum(self.worksites as w, w.payroll),
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    },
    Headquarters = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
        }
    },
```

> [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 {
>             headquarters: Worksite,
>             worksites: List(Worksite) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = self.headquarters.payroll + sum(self.worksites as w, w.payroll),
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Worksite = {
>         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 {
>             headquarters: Headquarters,
>             worksites: List(Worksite) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = self.headquarters.payroll + sum(self.worksites as w, w.payroll),
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Headquarters = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     },
>     Worksite = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-RECORD-RECURSIVE

**The record tree is finite.**

A field typed with a record type nests one record inside another. The check asks that nesting to end: a path down the fields passes each record type at most once.

A record type that holds itself, directly or through other records, describes a request of unbounded depth. The compiler needs a finite tree to list every input, to prove each calculation and to state the cost of a rating.

A record that sits under another record and also holds itself types two fields, so it gets `E-RECORD-POSITION` first.

**Example**

`Policy` holds its prior term as another `Policy`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            prior_term: Option(Policy)
        },
        step rating {
...
            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-RECORD-RECURSIVE bad/rater.rsl:21:5: record type `Policy` contains itself (E-RECORD-RECURSIVE), so its tree is not finite. Write a bounded list of a non-recursive record, such as `layers: List(Layer) where count(self) in [1, 20]`
```

**Fix**

Declare a separate record type with the inputs the nested record needs. A chain of similar records, such as layers, becomes a bounded list of one record type.

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

> [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],
>             prior_term: Option(Policy)
>         },
>         step rating {
>             rated_payroll: Num =
>                 match self.prior_term {
>                                Some(p) => max(self.payroll, p.payroll),
>                                None => self.payroll
>                            },
>             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
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             prior_term: Option(PriorTerm)
>         },
>         step rating {
>             rated_payroll: Num =
>                 match self.prior_term {
>                                Some(p) => max(self.payroll, p.payroll),
>                                None => self.payroll
>                            },
>             premium: Premium [cite: manual:p3] = round(self.rated_payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     PriorTerm = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-REF-SCOPE

**A reference points into a keyed list.**

A field typed `Ref(Worksite)` points at one record of a list in the same request. The request sends that record's key: a placement names its worksite by number.

The check asks for two things. The list of `Worksite` records is `keyed by` one of its inputs. The `Ref` field sits on a record below the one that holds the list.

A key names each record of the list once, so every reference resolves to exactly one record. The rater then reads through it, as in `self.worksite.hazard_factor`.

**Example**

`Placement` refers to a `Worksite`, and the list `worksites` lacks a key.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            worksites: List(Worksite) where len(self) in [1, 20],
            placements: List(Placement) where len(self) in [1, 200]
        },
```

```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-REF-SCOPE bad/rater.rsl:38:13: `Ref(Worksite)` points into the keyed list of `Worksite`, but no `List(Worksite) keyed by …` field exists (E-REF-SCOPE); key the list: `drivers: List(Worksite) keyed by number`
```

**Fix**

Key the list by the input that names each record once.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            worksites: List(Worksite) keyed by number where len(self) in [1, 20],
            placements: List(Placement) where len(self) in [1, 200]
        },
```

**Other causes**

* A `keyed by` that names a field the record lacks gets `` `Worksite` has no such input `numbr` ``. The key is an input that holds a number, a `Date`, an enum member or a `Code`.
* A `keyed by` on a field of another shape, such as one record, gets `` `worksites` is not a `List(R)` field ``.
* A `Ref` on the record that holds the list gets ``the field must sit on a record that `Policy` encloses``. Move the field to a record below it.

> [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 {
>             worksites: List(Worksite) where len(self) in [1, 20],
>             placements: List(Placement) where len(self) in [1, 200]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = sum(self.placements as p, p.charge)
>         }
>     },
>     Worksite = {
>         inputs {
>             number: Num {precision: int} where self in [1, 999],
>             hazard_factor: Num {precision: 0.01} where self in [0.5, 3]
>         }
>     },
>     Placement = {
>         inputs {
>             worksite: Ref(Worksite),
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             charge: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.worksite.hazard_factor, 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 {
>             worksites: List(Worksite) keyed by number where len(self) in [1, 20],
>             placements: List(Placement) where len(self) in [1, 200]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = sum(self.placements as p, p.charge)
>         }
>     },
>     Worksite = {
>         inputs {
>             number: Num {precision: int} where self in [1, 999],
>             hazard_factor: Num {precision: 0.01} where self in [0.5, 3]
>         }
>     },
>     Placement = {
>         inputs {
>             worksite: Ref(Worksite),
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             charge: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.worksite.hazard_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-ANCESTOR-READ

**A type name reads an enclosing record.**

A calculation reads values in three directions. `self.x` reads its own record, a field reads a child, and a record type name reads a record that encloses this one.

Inside `Worksite`, `Policy.experience_modifier` reads the one policy that holds the worksite. The check asks every read by type name to name such an enclosing record.

A record lower in the tree, or beside this one, can occur many times in a request, so its type name matches several records. A list field and a fold, or a `Ref` field, say which records the calculation means.

**Example**

`Policy` reads `Worksite.payroll`, and a policy holds a list of worksites.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            payroll: Num = Worksite.payroll,
            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-ANCESTOR-READ bad/rater.rsl:26:28: `Worksite` does not enclose `Policy`, so `Worksite.payroll` names no single record. A calculation reads its own fields (`self.x`), its children's through their fields, and an enclosing record by its type name; a sibling is read through a `Ref` field
```

**Fix**

Read the child records through the list field, and say how to combine them. Here the policy sums the payroll of its worksites.

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

**Other causes**

* A read of the record's own value by type name gets `` `Policy.payroll` names the record whose calculation this is ``. Read it as `self.payroll`.
* A read of a record beside this one gets the message of the example. Add a `Ref` field that names the record, and read through it.

> [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 {
>             worksites: List(Worksite) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = Worksite.payroll,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Worksite = {
>         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 {
>             worksites: List(Worksite) where len(self) in [1, 20]
>         },
>         step rating {
>             payroll: Num = sum(self.worksites as w, w.payroll),
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Worksite = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-INTERNAL

**Report a compiler fault to Effective.**

`E-INTERNAL` reports a fault in the compiler itself. The compiler met a case that its rules leave out, and it stops with this code in place of a wrong answer.

The message names what stopped it. Send the message to Effective with the rater that caused it, so the compiler gains a clear check message for that case.

Meanwhile, look at the place the message names. A rater that gets this code often writes a form another way than RSL does, and the rewrite clears the message.

**Example**

`sum` adds two numbers here, and in RSL `sum` folds a list or an enum.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            payroll: Num = sum(self.clerical_payroll, self.field_payroll),
            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-INTERNAL bad/rater.rsl:27:28: unsupported construct `scalar aggregation`
```

**Fix**

Add two values with `+`. Keep `sum` for a list, as in `sum(self.worksites as w, w.payroll)`.

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

**Other causes**

* A file in `enums/` saved in another encoding than UTF-8 gets `enum CSV … is not UTF-8`, together with `E-ENUM-BACKING`. Save the file as UTF-8.
* A path in `enums/` that the compiler fails to open gets `failed to read enum CSV …`.

> [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 {
>             clerical_payroll: Num {precision: int} where self in [0, 100000000],
>             field_payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             payroll: Num = sum(self.clerical_payroll, self.field_payroll),
>             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 {
>             clerical_payroll: Num {precision: int} where self in [0, 100000000],
>             field_payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             payroll: Num = self.clerical_payroll + self.field_payroll,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```


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