rulec diagnostics
Every code rulec can print, what makes it appear, and how to fix it. The code and the JSON shape are a stable API; only the prose improves. For one of them: rulec explain E101.
| Code | Severity | Title |
|---|---|---|
| E001 | error | Unterminated string |
| E002 | error | Unreadable character |
| E003 | error | The file does not start with a rule line |
| E004 | error | The line does not start with a word |
| E005 | error | A word that cannot appear at this position |
| E006 | error | The declaration has no = |
| E007 | error | No such policy |
| E008 | error | Empty cell |
| E009 | error | A declared name collides with a reserved word |
| E010 | error | The .. range notation is not allowed |
| E011 | error | A public name has no ASCII alias |
| E012 | error | The name is not declared |
| E013 | error | No such import |
| E014 | error | An output cell cannot hold an expression |
| E015 | error | result can only assemble the first output |
| E016 | error | There can be only one result |
| E017 | error | A constraint is not shaped like this |
| E018 | error | A constraint relates two inputs |
| E019 | error | An example is an input the generated code refuses at its door |
| E020 | error | The elements declaration is not right |
| E021 | error | The fold is not written correctly |
| E022 | error | The answer for a sequence with no elements is not declared |
| E023 | error | The answer for a walk that reached the end is not declared |
| E024 | error | Some verdict has no arm |
| E025 | error | The examples have no column for the sequence |
| E026 | error | The sequence is not written correctly |
| E027 | error | The example names a sequence that is not there |
| E028 | error | The count is not written correctly |
| E029 | error | This column cannot be counted |
| E030 | error | A count needs a range |
| E031 | error | One item reads the elements of two sequences |
| E032 | error | The declared enum and the imported one disagree |
| E033 | error | A value that came from elsewhere has neither a row nor default |
| E034 | error | A name appears twice |
| E035 | error | The target of overrides does not exist |
| E036 | error | The target of overrides does not define the same output |
| E037 | error | A cited fragment is not pinned |
| E038 | error | A source fragment has changed |
| E039 | error | There is no copy of a source |
| E040 | error | A file relied on differs from its pin |
| E041 | error | The bindings of an apply do not match the callee |
| E042 | error | A binding does not agree in type |
| E043 | error | A value passed leaves the callee's range or constraint |
| E044 | error | That rule cannot be applied |
| E045 | error | A table that shares an output has two or more output columns |
| E046 | error | A clause is not shaped like this |
| E047 | error | Extra token after the declaration |
| E049 | error | A thousands separator cannot be written |
| E048 | error | This type has no arithmetic |
| E050 | error | The machine section is not shaped right |
| E051 | error | carry or over does not fit the declarations |
| E052 | error | A state the machine names does not fit |
| E053 | error | The once line does not fit the declarations |
| E054 | error | A rule that folds a sequence cannot be the step of a machine |
| E055 | error | A scenario is not shaped right |
| E056 | error | The held line does not fit the declarations |
| E057 | error | The declaration has no type |
| E058 | error | A declaration line cannot be read |
| E059 | error | The expression cannot be read |
| E060 | error | A step is not positive |
| E061 | error | The range is empty |
| E062 | error | There is no such date |
| E063 | error | The cell cannot be read |
| E064 | error | The row does not fit the header |
| E065 | error | range from koyomi is misshapen or misplaced |
| E066 | error | An enums file, or an import enum from one, is misshapen |
| E069 | error | An ordered enum, a comparison on one, or a display name is misshapen |
| E071 | error | The prerequisites are misshapen |
| E072 | error | A carried value is not written right |
| E075 | error | A word is both a value of an enum and a name |
| E101 | error | Completeness gap: some input matches no row |
| E102 | error | Unreachable row: the row never matches |
| E103 | error | Unit mismatch: values of different types are being mixed |
| E104 | error | A numeric output declares no rounding |
| E105 | error | Overlapping rows: the same input matches two or more rows |
| E106 | error | An output literal is not on the rounding grid |
| E107 | error | An example does not match |
| E108 | error | Cannot prove an intermediate value fits in int64 |
| E109 | error | The check exceeded its budget, so completeness could not be proven |
| E110 | error | A column has a type the check cannot handle |
| E111 | error | The examples have no column for an output |
| E112 | error | The range of a derived value does not contain the values it can reach |
| E113 | error | The condition of a boolean definition is neither of the two allowed forms |
| E114 | error | A value does not sit on the declared step |
| E115 | error | The divisor is not a positive constant |
| E116 | error | A row's amount is not in the copy it cites, or is under another heading there |
| E117 | error | A share without what a share needs |
| E118 | error | The call is not written correctly |
| E120 | error | A from does not fit the input's type |
| E121 | error | The contract has no such path |
| E119 | error | A row's boundary falls on the other side from the copy it cites |
| W122 | warning | No input is projected from that shape |
| E122 | error | The contract lets through a value the rule refuses |
| W123 | warning | A row is reached only by values the contract does not let through |
| E123 | error | The contract lets through a combination the rule's constraint refuses |
| W124 | warning | A row is reached only by a combination the contract does not let through |
| E124 | error | A final state has a way out |
| E125 | error | A case can reach a state it can never finish from |
| E126 | error | A never line is broken |
| E127 | error | A once line is broken |
| E128 | error | The machine's claims could not be checked |
| E129 | error | This rulec reads no koyomi file |
| E130 | error | The days of the koyomi date cannot be read |
| E132 | error | The divisor can be zero |
| E139 | error | The argument of isqrt can be below zero |
| E140 | error | An output's range does not hold the values it takes |
| E134 | error | A call answers a carried number outside its range |
| E135 | error | An always line is broken |
| E136 | error | The prerequisites go round in a cycle |
| E137 | error | A value cannot be reached from start |
| E138 | error | A prerequisite comes after the value in the order of ordered by |
| W125 | warning | No sequence of calls reaches a state |
| W126 | warning | A transition is never taken from a state a case can reach |
| W127 | warning | A claim of the machine could not be settled |
| W128 | warning | No input was found that reaches a row, and none was shown not to |
| W130 | warning | A prerequisite is already needed through another |
| W105 | warning | Shadowing that needs review: an earlier row hides part of a later one |
| W110 | warning | A first table with no overlaps |
| W111 | warning | A declaration is never used |
| W116 | warning | No example uses this sequence |
| W119 | warning | A pinned fragment is not cited |
| W120 | warning | The copy states a value no row uses |
| W118 | warning | No row of an applied table is reached in this apply |
| W117 | warning | An exception with no effect |
| W121 | warning | An identifier collides, or cannot be used, in a target language |
| W115 | warning | No element can land on this verdict |
| W114 | warning | Unconfirmed overlap: an input may match both rows |
| W901 | warning | A key is written in the file |
| E074 | error | A from sum, from max or from min, or an elements … from, is misshapen |
| E070 | error | A random input or a claim of chances is not shaped right |
| E073 | error | A pick by weight is not shaped right |
| E133 | error | A claim of chances does not hold |
| W129 | warning | A claim of chances could not be decided within the budget |
| W131 | warning | The chances function runs the rule many times on every call |
| E067 | error | A contribution is not shaped as one |
| E068 | error | An open value is not shaped as one |
| E131 | error | A value put together leaves the range it declares |
| W132 | warning | A term of an open value changes nothing |
E001
error — Unterminated string
When. A string opened with " is not closed on the same line. Neither a cell nor a description may contain a line break.
Fix. Close the " on the same line. A long description either fits on one line or moves into a # comment.
Smallest reproduction:
Related codes: E002
E002
error — Unreadable character
When. A character that is neither a letter nor _ appears where a name is expected — an alias in parentheses that is not a name (sixty(60)) included. The check exists so that a typo does not quietly become a name, nor an alias quietly go missing.
Fix. Delete the character. A name starts with a letter or _ (@x(x) becomes x(x), and sixty(60) becomes sixty(size_60)).
Smallest reproduction:
E003
error — The file does not start with a rule line
When. One .rule is one rule, and its first line carries the name and the version. Anything else comes before it, or the rule line has no name — a file whose rule line had none used to pass the check as a rule with nothing in it.
Fix. Add rule <name>(<alias>) v1 as the first line.
Smallest reproduction:
E004
error — The line does not start with a word
When. A line that is neither a table row, a comment nor blank starts with a symbol. The syntax is line-oriented: the first word of a line decides what is being declared.
Fix. Start the line with a declaring word (description / import / enum / group / inputs / elements / outputs / derive / define / constraint / table / fold / count / sum / max / min / sequence / result / examples / policy / overrides / clause / source / apply / shape / machine / scenario / prerequisites / chances / open). A table row starts with |.
Smallest reproduction:
E005
error — A word that cannot appear at this position
When. The word at the head of the line is not in the vocabulary. The vocabulary has no synonyms: one English spelling each.
Fix. Correct it to one of description / import / enum / group / inputs / elements / outputs / derive / define / constraint / table / fold / count / sum / max / min / sequence / result / examples / policy / overrides / clause / source / apply / shape / machine / scenario / prerequisites / chances / open. Business words belong in names and cells, not at the head of a line.
Smallest reproduction:
E006
error — The declaration has no =
When. enum, group, derive, define and result separate the name from the body with =. It is missing.
Fix. Insert the =, e.g. enum k(k) = a(a) | b(b).
Smallest reproduction:
Related codes: E005
E007
error — No such policy
When. The word after policy is not unique or first. DMN's Any, Priority and Collect are not adopted.
Fix. Write either policy unique (every overlap is an error) or policy first (the earlier row wins).
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : bool
table j(j)
policy any
| x | -> r(r) : bool |
| - | true |
Related codes: W110, E105, W105
E008
error — Empty cell
When. A cell of a table holds only whitespace. A blank is indistinguishable from a forgotten entry, so it is a syntax error.
Fix. If any value is meant, write -. If a value was forgotten, write the value.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| | true |
Related codes: E101
E009
error — A declared name collides with a reserved word
When. A declared name (or alias) is the same as a word of the vocabulary. Otherwise the line-oriented parser reads the line as the start of a section and drops the declaration silently. An enum value never starts a line, so for a value it is only the eleven words its own position reads as something else (not none true false starts_with default after empty exhausted by with).
Fix. Rename it (enum range(kind) becomes enum range_band(range_kind)). The reserved words are fixed by the one table in src/kw.rs.
Smallest reproduction:
E010
error — The .. range notation is not allowed
When. A cell contains .., as in 0g..1000g. Whether "up to 1000g" includes the endpoint cannot be read off the text.
Fix. Rewrite it with comparison operators: 0g..1000g is either <=1000g or <1000g. To fix both ends, write >=0g <=1000g.
Smallest reproduction:
rule t(t) v1
inputs
w(w) : mass[g] range >=0g <=10kg
outputs
r(r) : bool
table j(j)
policy unique
| w | -> r(r) : bool |
| 0g..1000g | true |
| >1000g | false |
E011
error — A public name has no ASCII alias
When. The rule name, an input, an output, a sequence or a field of one element has a non-ASCII name and no alias in parentheses. The alias becomes the public name in the generated code (a kanji has no uppercase and cannot begin an exported Go identifier, and the name of a field of the .proto is ASCII alone). A sequence is an input the caller passes, and the fields of one element are what the caller fills in, so they are as public as an input. A name that is already ASCII is its own public name and needs no alias.
Fix. Add the alias in parentheses (重量 : bool becomes 重量(weight) : bool). On a derived value, a definition, a group or a table the alias is optional: write one and the generated code uses it, leave it out and the declared name is used as it stands.
Smallest reproduction:
rule t(t) v1
inputs
重量 : bool
outputs
r(r) : bool
table j(j)
policy unique
| 重量 | -> r(r) : bool |
| - | true |
A sequence with no ASCII alias:
rule pieces_total v1
elements pièces
amount : money[USD] range >=0USD <=100USD
outputs
total : money[USD] round down(1USD)
sum spent over pièces of amount range >=0USD <=1000USD
define total : money[USD] = spent
E012
error — The name is not declared
When. A column or an expression names something that is declared nowhere. There is no forward reference: a name is declared above the line that uses it. It also appears when a value written in a cell or a group is a value of no enum; when the rule imports a built-in namespace of divisions (std/us/states and the rest), the note gives the closest spelling, or the namespace of another country that has the word. And it appears when a word is a spelling two imported countries share (WA is the code of Washington and of Western Australia). none is not one of these: under an optional column it is the absent value, and under any other column it is E103.
Fix. Match the spelling to the declaration, or declare the input, derived value or definition above. For a spelling two countries share, write the value's name (Washington or Western_Australia).
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : bool
table j(j)
policy unique
| y | -> r(r) : bool |
| - | true |
E013
error — No such import
When. The target of import is not there. There are three kinds: the built-in namespace (the first-level divisions of thirteen countries: std/us/states, std/gb/nations, std/cn/provinces, std/tw/divisions, std/kr/provinces, std/in/states, std/fr/regions, std/es/communities, std/it/regions, std/de/states, std/au/states, std/br/states and std/jp/prefectures, whose prefectures std/都道府県 also imports, with the values spelled in Japanese), an enum in a .proto (import proto "<file>" <Enum> -> <enum of this rule>), and an enum in a JSON Schema (import jsonschema "<file>" "<pointer>" -> <enum of this rule>, OpenAPI included). For the last two it appears when the file cannot be read, when it holds no such enum, or when the line is not that shape. YAML is not read; point at a JSON form of it. It also appears when one country's divisions are imported under both of its names (std/都道府県 and std/jp/prefectures).
Fix. Correct the namespace's spelling (the note gives the closest one), or declare the enum in this file with enum. When importing from a file, the path is followed from the directory of the rule file, so write it relative to that. A JSON Schema pointer looks like #/components/schemas/<name>, and one that does not resolve comes back with the keys that are there.
Smallest reproduction:
E014
error — An output cell cannot hold an expression
When. A cell to the right of -> holds two or more words. Only one value or one name may be written there. Taking just the first word and skipping the rest would emit generated code with the multiplication silently dropped.
Fix. Give the calculation a name on a define line and leave only that name in the table (| - | rate_discount |). A table holds the branching and nothing else.
Smallest reproduction:
rule t(t) v1
inputs
p(p) : money[JPY, incl_tax] range >=0JPY <=10_000JPY
r(r) : rate[step 1%] range >=0% <=100%
outputs
o(o) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy first
| r | -> o(o) : money[JPY, incl_tax] |
| <=5% | 0JPY |
| - | p * r |
E015
error — result can only assemble the first output
When. A result names an output other than the first. result is sugar for the first output, and both the evaluator and the generated code apply it only there. The name used to be ignored, so a number could land in a money slot without an E103.
Fix. Write a define of the same name as that output (define points(pts) : number = base_points * multiplier); outputs are taken, in declaration order, from the binding of their own name. To assemble it with result instead, move that output to the top of outputs.
Smallest reproduction:
rule t(t) v1
inputs
p(p) : money[JPY, incl_tax] range >=0JPY <=10_000JPY
outputs
a(a) : money[JPY, incl_tax] round down(1JPY)
b(b) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| p | -> a(a) : money[JPY, incl_tax] |
| - | 100JPY |
result b = p
E016
error — There can be only one result
When. A file has more than one result line. Only the first output can be assembled, so a second result merely replaces the first — which it used to do in silence.
Fix. Keep one. The other outputs are taken from a define of the same name as the output.
Smallest reproduction:
rule t(t) v1
inputs
p(p) : money[JPY, incl_tax] range >=0JPY <=10_000JPY
outputs
a(a) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| p | -> a(a) : money[JPY, incl_tax] |
| - | 100JPY |
result a = p
result a = p + 100JPY
Related codes: E015
E017
error — A constraint is not shaped like this
When. A constraint line is not input comparison input: there is no comparison, a side is neither one name nor names added up with +, or both sides are sums.
Fix. Write constraint <input> <= <input>; the comparisons are <=, <, >= and >. One side may add inputs up (constraint discount + points <= subtotal); a coefficient or a constant cannot be written. For A = B, write the two lines A <= B and A >= B.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : number range >=0 <=10
b(b) : number range >=0 <=10
constraint a
outputs
r(r) : bool
table j(j)
policy unique
| a | -> r(r) : bool |
| - | true |
Related codes: E018
E018
error — A constraint relates two inputs
When. A side of a constraint is not an input, or has a type with no order. A constraint says which combinations of the values the caller passes can happen, so both sides name something in inputs, and each is money, a quantity, a rate, a number or a date. Every name a sum adds is held to the same.
Fix. Name inputs on both sides. A derived or defined value is computed from inputs, so write the relation between those inputs; an enum and a boolean have no order.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : number range >=0 <=10
constraint a <= r
outputs
r(r) : bool
table j(j)
policy unique
| a | -> r(r) : bool |
| - | true |
E019
error — An example is an input the generated code refuses at its door
When. An example's inputs do not satisfy a constraint, or the value of an input — a field of an element of the sequence included — lies outside its declared range. A constraint declares that a combination does not happen and a range that no value outside it arrives; the completeness check believed them and demanded no row there, and the generated code refuses that input at the door. It is not an input an answer can be claimed for. An example outside the range, and one that breaks a constraint between two dates, used to pass the check, and the vector made from it was then refused by the generated code.
Fix. Correct the example's values — or, if that value or combination really does arrive, widen the range or drop the constraint.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : number range >=0 <=10
b(b) : number range >=0 <=10
constraint a <= b
outputs
r(r) : bool
table j(j)
policy unique
| a | b | -> r(r) : bool |
| - | - | true |
examples
| a | b | -> r |
| 5 | 1 | true |
Related codes: E017, E018, E101
E020
error — The elements declaration is not right
When. An elements line has no name, or nothing walks the sequence it declares. A sequence becomes a value of the rule only when a count, a sum, a max, a min or a fold names it after over; an item that reads a field of a sequence nothing walks reads no element in particular. A rule may walk any number of sequences, each under an elements of its own.
Fix. Write elements fee_rows(fee_rows), and the fields of one element under it, declared the way inputs are. If the sequence is needed, add the line that walks it; if not, take the elements out.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
elements xs(xs)
k(k) : number range >=0 <=10
outputs
r(r) : bool
table j(j)
policy unique
| n | -> r(r) : bool |
| - | true |
Related codes: E028, E021, E031
E021
error — The fold is not written correctly
When. The heading is not fold <verdict column> over <sequence> [-> <name> [: <type>]], or an arm is not one of next, stop, stop with <value>, take_unique <value>, take_first <value>, keep_max <value> by <key>, take_weighted <value> by <weight>, or the column being folded is not an enum. So is an answer named after an input, a field of an element, a sequence or a value declared above; a name that is no output given no type; an output's name given a type other than the output's; and a name that the fold and another item both decide. So, too, is a value of the fold that reads what is not there when it is decided: empty and exhausted are decided where no element is being looked at and cannot read an element's value; no value can read what is computed from the fold's own answer (a verdict that read it would need the answer before there is one); and held, what the walk holds once it has seen every element, belongs in exhausted only.
Fix. Correct the heading and the arms. A bare take cannot be written: whether one and only one element may be taken, or the first of several, is for the author to choose. To answer with a value of an element, take it with take_unique, take_first or keep_max, and answer exhausted -> held. Name the answer with -> <name> for the items below to read it; a verdict that needs a fold's answer goes in another fold, which reads it on a second walk.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
elements xs(xs)
k(k) : number range >=0 <=10
outputs
r(r) : bool
table j(j)
policy unique
| n | -> r(r) : bool |
| - | true |
fold r
empty -> false
Related codes: E020, E022, E023, E024
E022
error — The answer for a sequence with no elements is not declared
When. A fold has no empty -> <value>, or a max or a min has no empty <value>. An empty sequence always turns up, and it is the case a hand-written loop most often forgets — usually by reading the first element and falling over. For a max and a min, it is also the answer when no element passes the where.
Fix. Add empty -> <value> (empty <value> on a max or a min). What to answer is a business decision, and not one the tool can make.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
exhausted -> held
A max with no empty:
rule t(t) v1
elements xs(xs)
k(k) : number range >=0 <=10
outputs
r(r) : number round down(1)
max r over xs of k
E023
error — The answer for a walk that reached the end is not declared
When. A fold has no exhausted -> <value>: the answer when the sequence ran out and no element ended the walk. Answering with the value that was held is a choice, and it is made by writing it.
Fix. Add exhausted -> <value>; to answer with what is held, that is exhausted -> held.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
empty -> 0JPY
E024
error — Some verdict has no arm
When. A verdict the table can produce has no arm in the fold: when an element lands on it, the walk has no move. It is the table's own completeness check, applied to the fold.
Fix. Add the arm, or stop the table producing that value. The other direction — an arm for a verdict nothing can reach — is W115.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
empty -> 0JPY
exhausted -> held
Related codes: E022, E023, W115
E025
error — The examples have no column for the sequence
When. A rule that walks a sequence has examples, but the header has no column named after its elements. An example that does not say which sequence it walks is an example with no answer.
Fix. Write the list with sequence <name>, add a column for the sequence to the examples header, and name it in the cell. A sequence with no rows is the example for a sequence with nothing in it.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
empty -> 0JPY
exhausted -> held
examples
| -> r |
| 0JPY |
E026
error — The sequence is not written correctly
When. The columns of a sequence do not line up with the fields of elements: a column that is not a field, a field left out, one field's column twice, a ->, no sequence to be a list of, two blocks with the same name, or a cell that is not a value (a range or a -).
Fix. Make the header the fields of elements as they are, and write one element's values per row. This is not a table: it is the list of values as they would really be passed.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
empty -> 0JPY
exhausted -> held
sequence s(s)
| m |
| 3JPY |
E027
error — The example names a sequence that is not there
When. The cell in the sequence column names a sequence that is not declared, or holds something that is not a name at all.
Fix. Write a sequence under that name, or correct the cell to one that is written.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
empty -> 0JPY
exhausted -> held
examples
| xs | -> r |
| nope | 0JPY |
E028
error — The count is not written correctly
When. The line is not count <name>(<alias>) over <sequence> where <column> = <value>: no over, no where, a sequence that elements does not declare, or an = with nothing on its right. So is a sum that is not sum <name>(<alias>) over <sequence> of <column>, and a max or a min that is not <name>(<alias>) over <sequence> of <column> [where <column> [= <value>]] empty <value>.
Fix. Write it in that shape. The = <value> may be left out only for a bool column.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
elements xs(xs)
b(b) : bool
outputs
r(r) : bool
count h(h) where b range >=0 <=10
table j(j)
policy unique
| n | -> r(r) : bool |
| - | true |
Related codes: E029, E030, E020
E029
error — This column cannot be counted
When. The column where names is not a value of one element (an input or a derived value is one per call, so counting it could only answer 0 or 1); or its values are not a closed set; or the value written is not one of that enum's; or an enum column was given no = <value>. The column after of on a sum, a max or a min is held the same way, and it also appears when that column is a value of another sequence than the one over names, when a sum reads no number, and when a max or a min reads neither a number, a date nor an ordered enum (their title then says the column cannot be summarised).
Fix. Name a field of an element, or a column a per-element table produces. Writing the classification as a table is what puts the classification itself under the completeness check.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
ok(ok) : bool
elements xs(xs)
b(b) : bool
outputs
r(r) : bool
count h(h) over xs where ok range >=0 <=10
table j(j)
policy unique
| h | -> r(r) : bool |
| - | true |
E030
error — A count needs a range
When. The count line has no range >=0 <=<max>, or no upper bound, or a negative lower one. The range means two things: the universe the completeness check quantifies over once the count is a column, and the cap on the sequence.
Fix. Write it as range >=0 <=100. The generated code refuses a longer sequence at the door, the way it refuses a number outside its range.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
elements xs(xs)
b(b) : bool
outputs
r(r) : bool
count h(h) over xs where b
table j(j)
policy unique
| h | -> r(r) : bool |
| - | true |
E031
error — One item reads the elements of two sequences
When. One item (a table, a clause, a derive, a definition or a fold) reads a value of one element of each of two sequences. A value of one element exists only while its sequence is walked, so reading both together would walk one sequence again from the start at every element of the other — a walk inside a walk. The number of walks would then grow with the length of a sequence, and the checks could not finish. So is an output that is a value of one element: once the walk is over, it is not one value. A rule's items fall into those that read no element and those of one element, and an item of one element is worked out on the walk after the one that finds every summary it reads (an item of one element that reads a summary goes on a second walk).
Fix. Make one of the sequences one value first — a count, a sum, a max, a min or a fold — and read that value in the item of the other; the item is then worked out on a second walk, once the value is known. To answer with something each element decides, make it one value for the whole sequence with a summary first.
Smallest reproduction:
rule t(t) v1
elements xs(xs)
a(a) : number range >=0 <=10
elements ys(ys)
b(b) : number range >=0 <=10
outputs
r(r) : number round down(1)
s(s) : number round down(1)
define both(both) : number = a + b
sum r over xs of both range >=0 <=1000
sum s over ys of b range >=0 <=1000
An output that is a value of one element:
rule big_parts v1
elements parts
price : number range >=0 <=100
outputs
total : number round down(1)
big : bool
table big_of
policy unique
| price | -> big : bool |
| >=50 | true |
| <50 | false |
sum total over parts of price range >=0 <=10000
Related codes: E021, E029, E020
E032
error — The declared enum and the imported one disagree
When. The values of the enum named by import proto or import jsonschema and the values of the rule's enum (their ASCII aliases, where they have them) are not the same set. Values on either side alone are named, in both directions. It is usually the proto that gained one, and it shipped as a compatible change made outside this rule.
Fix. Add the new value to the rule's enum; where the enum gives its values names with aliases, the name is yours to choose. A value the contract dropped goes from the rule too. Once it is added, the table asks whether it needs a row (E033).
Smallest reproduction:
rule t(t) v1
import proto "tier.proto" Tier -> v
enum v(v) = one(one)
inputs
x(x) : v
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| one | true |
With tier.proto beside it:
Related codes: E013, E033, E101
E033
error — A value that came from elsewhere has neither a row nor default
When. A value of an imported enum appears in no row and is not marked default. The same holds for a value a contribution adds with extend that no row of a table names and that falls in silence to a row of -, a not: that does not leave it out, or a comparing cell that now covers it (each such row is named). For a value you wrote yourself that is a forgotten line (W111); for a value that came through a contract or a contribution it means a change from elsewhere that nobody has read yet, so it stops. With a default row the completeness check passes and the new value quietly takes the default row's answer.
Fix. Add a row for it, or mark the value default in the declaration. For a value a contribution adds, have a contribution add its row to the open table, or put default on the extend line. default is a signature saying that falling through to the default row is what is meant — that someone decided how it is treated.
Smallest reproduction:
rule t(t) v1
import proto "tier.proto" Tier -> v
enum v(v) = one(one) | two(two)
inputs
x(x) : v
outputs
r(r) : bool
table j(j)
policy first
| x | -> r(r) : bool |
| one | true |
| - | false |
With tier.proto beside it:
A value a contribution adds (extend):
With base.rule beside it:
rule base(base) v1
enum kind(kind) = a(a) | b(b) open
inputs
k(k) : kind
outputs
fee(fee) : number round down(1)
table t(t)
policy first
| k | -> fee : number |
| a | 1 |
| - | 2 |
Related codes: E032, W111, E101, E067
E034
error — A name appears twice
When. A file writes one name twice among the names of one kind, and it is said at the second. The kinds are the values (the inputs, the sequences and the fields of their elements, the outputs, and what derive, define, count, sum, max, min, open and the output columns of tables and clauses decide), the tables and clauses, the enums, the values of one enum, the groups and the values one lists, the shapes, the sources, the applies, the prerequisites blocks, the labels of the rows of one table, and the columns of the heading of a table or of the examples. An output shares its name with what decides it, and several tables and clauses may decide one name; any other two values of one name stop here, an output named like an input among them. A group named like a value of an enum is one word read two ways, and stops here too. So do two names of one list that come to one identifier in the generated code (the alias of one is the alias or the name of the other), one name given two aliases, and, in an enums file, an enum's name that is another enum's alias (import enum finds an enum by either). So do two names whose identifiers differ but which every language that writes them spells as one: the types of two enums and two values of one enum (both written in PascalCase), the functions of two prerequisites blocks (in snake_case), the element types of a rule's two sequences, and a shape beside an input or a sequence among the parameters of the function that reads the caller's objects. Two that only some languages spell as one are W121. An enum read with import enum, and a group that comes with it, is held to the names of this rule and of the other import enum lines, and an input a contribution adds to the names of the rule it adds to. A fold's answer is held by E021, and the name an apply gives a callee output by E041.
Fix. Remove one of the two, or rename one (a row label and a column of a heading alike). Of two names that come to one identifier, give one another alias. An enum read with import enum can be read under another name with -> <another name>.
Smallest reproduction:
rule weight_check v1
inputs
weight : number range >=0 <=9
weight : number range >=0 <=99
outputs
heavy : bool
table decide
policy unique
| weight | -> heavy |
| <=5 | false |
| >5 | true |
One value written twice in an enum:
rule free_shipping v1
enum member_kind = basic | gold | basic
inputs
member : member_kind
outputs
free : bool
table decide
policy unique
| member | -> free |
| basic | false |
| gold | true |
Two names that come to one identifier:
rule size_check v1
inputs
weight(w) : number range >=0 <=9
width(w) : number range >=0 <=9
outputs
large : bool
table decide
policy first
| weight | width | -> large |
| >5 | - | true |
| - | >5 | true |
| - | - | false |
One column twice in a table's heading:
rule weight_check v1
inputs
weight : number range >=0 <=9
outputs
heavy : bool
table decide
policy first
| weight | weight | -> heavy |
| <=5 | >5 | true |
| - | - | false |
Two enums whose types take one name:
rule tier_fee v1
enum tier_level = basic | gold
enum tierLevel = low | high
inputs
member : tier_level
band : tierLevel
outputs
fee : money[USD] round down(1USD)
table fee_by_member
policy unique
| member | band | -> fee |
| basic | - | 1USD |
| gold | low | 2USD |
| gold | high | 3USD |
One label on two rows of a table:
rule t(t) v1
inputs
a(a) : bool
outputs
x(x) : bool
table t1(t1)
| a | -> x |
r1 | true | true |
r1 | false | false |
Related codes: E009, E035, E021, E041, E075, W121
E035
error — The target of overrides does not exist
When. An overrides line names a table that does not exist, or one declared below this table, or a row label (table:label) the table has no row of. A line whose shape cannot be read is reported the same way. The exception is written after what it excepts, so a target is always above.
Fix. Name a table declared above, or one of its rows (label the row at its head and write table:label). Write the side that takes precedence later.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
x(x) : bool
table t1(t1)
overrides no_such_table
| a | -> x |
| true | true |
| false | false |
E036
error — The target of overrides does not define the same output
When. The table an overrides line names defines a different output from this table. Precedence exists only between definitions of the same output.
Fix. Name a table that defines the same output, or make this table's output column the same one.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
x(x) : bool
y(y) : bool
table base(base)
| a | -> x |
| - | true |
table special(special)
overrides base
| a | -> y |
| - | true |
E037
error — A cited fragment is not pinned
When. A fragment cited with @source fragment has no fragment sha256:… pin line under its source line; for a file source, the line carries no sha256:…. A law cited with no article (@source alone), and a fragment name, a citation or a source line whose shape cannot be read, are reported the same way (a file beside the rule may be cited whole, @postal). A document's fragments are its tables, so table3 (the third table in document order; 表3 is its Japanese spelling) is the only name read. A fragment the language cannot read as one word is quoted (@osha "§1910.157"). Without a pin, a revised copy passes check in silence.
Fix. Once the transcribed rows are checked against the document, paste the fix.text line or run rulec source pin <file.rule> to pin the copy's digest.
Smallest reproduction:
rule t(t) v1
source 法 = law "000AC0000000001" asof 2026-04-01
inputs
a(a) : bool
outputs
x(x) : bool
table 表(t1) @法 第1条
| a | -> x |
| - | true |
With sources/law/000AC0000000001@2026-04-01/MainProvision-Article_1.xml beside it:
<Article Num="1"><ArticleTitle>第一条</ArticleTitle><Paragraph Num="1"><ParagraphNum/><ParagraphSentence><Sentence>甲は、乙とする。</Sentence></ParagraphSentence></Paragraph></Article>
Related codes: E038, E039, W119
E038
error — A source fragment has changed
When. The pinned digest differs from the digest of the copy beside the rule. It fails in the pull request that refreshed the copy, and names the tables, clauses and rows that cite the fragment, which is all there is to reread.
Fix. Read the copy's diff; if the transcribed rows still hold, rewrite the pin line as fix.text says (rulec source pin writes it too). If the rows have to change, change them first.
Smallest reproduction:
rule t(t) v1
source 法 = law "000AC0000000001" asof 2026-04-01
第1条 sha256:0000000000000000
inputs
a(a) : bool
outputs
x(x) : bool
table 表(t1) @法 第1条
| a | -> x |
| - | true |
With sources/law/000AC0000000001@2026-04-01/MainProvision-Article_1.xml beside it:
<Article Num="1"><ArticleTitle>第一条</ArticleTitle><Paragraph Num="1"><ParagraphNum/><ParagraphSentence><Sentence>甲は、乙とする。</Sentence></ParagraphSentence></Paragraph></Article>
E039
error — There is no copy of a source
When. The copy of a cited fragment is not beside the rule — sources/law/<law id>@<date>/<element>.xml for a law, tariff.md.fragments/table3.tsv beside the document for a document's table — or the file source itself cannot be read. check reads neither the network nor the document, so without a copy there is nothing to compare.
Fix. rulec source fetch <file.rule> fetches the fragment from e-Gov, the Japanese government's statute database, and takes the cited tables out of a document, into the copies beside the rule. Commit the copies.
Smallest reproduction:
rule t(t) v1
source 法 = law "000AC0000000001" asof 2026-04-01
inputs
a(a) : bool
outputs
x(x) : bool
table 表(t1) @法 第2条
| a | -> x |
| - | true |
E040
error — A file relied on differs from its pin
When. An apply heading carries no pin, or the pin differs from the callee beside the rule. The pin is meaning:… — the callee's rows and declarations with its comments, description and citations left out — and a sha256:… written before (the file's bytes) is still read. When the callee's rows are amended, the answers of the rule that applies it change too. The same holds for the signature:… on a contribution's to and after lines, missing or not matching: it is the shape of the file added to (its name, inputs and outputs, open values and outputs, open enums and blocks, and the declarations of the names the contribution reads), so a row or a comment of that file does not move it, and when the shape moves the diagnostic says what moved (when the repository's history holds the version it was pinned at). Without a pin, the change passes with nobody approving it.
Fix. See with rulec diff <old> <new> how many answers of this rule move and by how much; once the movement is approved, rewrite the heading as fix.text says or run rulec source pin <file.rule> to pin the callee again. For a contribution, read it against the new shape, then rewrite the signature: with rulec source pin <contribution>.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=1 <=10
outputs
y(y) : number round down(1)
apply call(c) = "callee.rule"
a = n
x -> y
With callee.rule beside it:
rule callee(callee) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
small | <=5 | 1 |
large | >5 | 2 |
A contribution's signature: differs from the shape it adds to:
With base.rule beside it:
rule base(base) v1
enum kind(kind) = a(a) | b(b) open
inputs
k(k) : kind
outputs
fee(fee) : number round down(1)
table t(t)
policy first
| k | -> fee : number |
| a | 1 |
| - | 2 |
Related codes: E037, E038, E044, E067
E041
error — The bindings of an apply do not match the callee
When. A callee input is left unbound or bound twice, a binding or an output line names something the callee does not have, the name given to a callee output is already declared in this rule, one callee output is given two names, or the apply block is not shaped as one. Substitution is written by binding every input explicitly, so a missing binding is a substitution left unwritten.
Fix. Bind each callee input with one <callee input> = <value> line, and rename an output with <callee output> -> <name>. The callee's input and output names are listed in the message.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=1 <=10
outputs
y(y) : number round down(1)
apply call(c) = "callee.rule" sha256:1c554496c37f709f
b = n
x -> y
With callee.rule beside it:
rule callee(callee) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
small | <=5 | 1 |
large | >5 | 2 |
Related codes: E040, E042, E043
E042
error — A binding does not agree in type
When. The type of a bound value differs from the callee input's (unit, tax kind, an enum against a number). Between two enums: a value of this rule's enum stands for no value of the callee's (values spelled the same on both sides map by themselves), a with names a value neither side has, or a literal is not a value of the callee's type. with none -> <value> is written only where this rule's value may be absent and the callee's input takes no absent value; written anywhere else, it is this code.
Fix. Make the types agree. For enums, <input> = <value> with <this rule's value> -> <callee's value>, … maps every value of this rule's enum; that is the shape of "'retirement' is read as 'end of term'". Write with none only where the value may be absent; where the callee takes the absent value, leave it out of with and pass it on as it is.
Smallest reproduction:
rule t(t) v1
enum kind(kind) = low(a) | mid(b) | high(c)
inputs
k(k) : kind
outputs
y(y) : number round down(1)
apply call(c) = "enum_callee.rule" sha256:5c19644247d1b672
a = k
x -> y
With enum_callee.rule beside it:
rule enum_callee(enum_callee) v1
enum band(kind) = low(a) | mid(b)
inputs
a(a) : band
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
| low | 1 |
| mid | 2 |
E043
error — A value passed leaves the callee's range or constraint
When. The interval of a bound value reaches outside the callee input's range (the point outside is shown as the witness), or a constraint of the callee does not follow from this rule's declarations. For the range of an input of an ordered enum (range >=basic <=gold), it is a value the bound value can take, read as the callee's through with, that the range does not let in. What a value can take is an input's range (every value of its enum without one), a literal's value, what the rows of a table write for its output (what a name can take, for a row that writes one), and, for a max or a min of two values, what the two can take that the answer can be (a max comes to nothing before the later of their first values, a min to nothing after the earlier of their last ones). A value a fold or another expression decides is taken to be any value of its enum, since check does not work out which ones those come to, and what is passed is then said to be not known. The callee's completeness was proved over that range and constraint; outside them there is no definition. It is also a value that may be absent — an input or a field of an element declared T?, the output of a table a row of which answers none, a definition that only names one — passed to an input or a field of the callee that is not optional: the callee's tables have no answer for the absent value. Where with none -> <value> reads it as a value of the callee's, there is nothing to say. There is no fix.text: adding a row to the callee belongs to another unit of approval.
Fix. Narrow this rule's input range to the callee's, or define the region outside it in a clause of this rule; which is a business decision. For an enum, declare the same range on this rule's input (of an ordered enum), decide the value passed in a table that writes only values inside the range, or read the values outside it as values inside it with with. For a constraint, declare the same relation with constraint. For the absent value, read it as a value of the callee's with with none -> <value> (an enum), give this rule's name a type that is not optional where it always has a value, or decide the value in a table of this rule that answers none too and pass that. A number or a field of an element that can be absent needs the callee to take the absent value, which is a change to the callee.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=10
outputs
y(y) : number round down(1)
apply call(c) = "callee.rule" sha256:1c554496c37f709f
a = n
x -> y
With callee.rule beside it:
rule callee(callee) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
small | <=5 | 1 |
large | >5 | 2 |
The range of an input of an ordered enum:
rule t(t) v1
enum tier = basic < silver < gold < platinum
inputs
k(k) : tier
outputs
y(y) : number round down(1)
apply call(c) = "tier_callee.rule" meaning:137f2373042d3154
campaign = k
x -> y
With tier_callee.rule beside it:
rule tier_callee(tier_callee) v1
enum tier = basic < silver < gold < platinum
inputs
campaign(campaign) : tier range >=basic <=gold
outputs
x(x) : number round down(1)
table t(t)
policy unique
| campaign | -> x |
| basic | 1 |
| >=silver | 2 |
A value that may be absent:
rule t(t) v1
enum tier = basic < silver < gold < platinum
inputs
m(m) : tier?
outputs
y(y) : number round down(1)
apply a(a) = "absent_callee.rule" meaning:38fa5f7ac6342966
t = m
x -> y
With absent_callee.rule beside it:
rule absent_callee(absent_callee) v1
enum tier = basic < silver < gold < platinum
inputs
t(t) : tier
outputs
x(x) : number round down(1)
table t1(t1)
policy unique
| t | -> x |
| basic | 1 |
| >=silver | 2 |
Related codes: E041, E042, E101
E044
error — That rule cannot be applied
When. The callee cannot be read, does not pass check (the codes are listed), or the applies go round: a rule applies a rule that applies it back, directly or through others (every file of the ring is listed). A broken rule expanded is a broken rule, and a rule that reads itself through itself has no answer. A rule that applies another, one that walks a sequence, and one whose tables produce its own enums can all be applied. It is also an output of the callee that is a number that may have no value (money[JPY]?): an applied output becomes a definition rounded as the callee declares, and a value that may be absent cannot be rounded. An optional enum, truth value, date or string can be applied, and hands the absent value on.
Fix. Fix the callee first. For a ring, take one of the apply lines out.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=1 <=10
outputs
y(y) : number round down(1)
apply call(c) = "broken_callee.rule" sha256:765fd7738ae6bfc1
a = n
x -> y
With broken_callee.rule beside it:
rule broken_callee(broken) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
| <=5 | 1 |
Applies that go round:
rule t(t) v1
inputs
n(n) : number range >=1 <=10
outputs
y(y) : number round down(1)
apply o(o) = "other.rule"
a = n
x -> y
With other.rule beside it:
rule other(other) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
apply back(back) = "explain.rule"
n = a
y -> x
E045
error — A table that shares an output has two or more output columns
When. An output is defined by two or more tables (or tables are joined by overrides), and one of them has two or more output columns. A row that bundles two definitions leaves the other value's origin undecided when only one is overridden, and the generated code would have to write the row's condition twice.
Fix. Move the second output to a table of its own.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
x(x) : bool
y(y) : bool
table base(base)
| a | -> x | y |
| - | true | true |
table special(special)
overrides base
| a | -> x |
| true | false |
E046
error — A clause is not shaped like this
When. The clause heading lacks a name, the -> or the output; the when or then line is missing or appears twice; or the when condition is not <column> <cell> and … (a part without a column, a column twice, a column without a condition). A clause is a one-row table, so it needs exactly one condition and one value.
Fix. Under clause <name>(<alias>) -> <output>, write one when <column> <cell> and … line (when always when there is no condition) and one then <value> line. Add overrides <target> when it takes precedence over something.
Smallest reproduction:
Related codes: E008, E035, E045
E047
error — Extra token after the declaration
When. A declaration line holds a word the line does not read. Each kind of line reads its own words: an input's range, contract_only and random; a field of an element's range and contract_only; an output's range and round; a derive and a count, sum, max or min line range alone. A shape line holds nothing after its declaration, and an input's from nothing after its path but where and empty. The readers look along the line for the word they want and step over everything else, so such a word used to be dropped in silence: a tax flag written after the range, as in range >=0JPY <=10_000JPY incl_tax, what is left of a bound whose unit did not lex as one, a round on a derive, a range after a from. A range is the universe the completeness proof quantifies over and the entry guard of the generated code, so a bound lost this way is answered "complete" with one side missing.
Fix. Remove the word, or write it in the form the declaration takes. A tax flag or a step goes inside the type's brackets (money[JPY, incl_tax], rate[step 0.1%]); a range is range >=<value> <=<value>; a rounding is round <mode>(<grid>). An input's range and marks go before its from; a derived value is rounded inside its expression, as down(<expression>, 1JPY).
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY, incl_tax] range >=0JPY <=10_000JPY incl_tax
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
Related codes: E011, E103, E104
E049
error — A thousands separator cannot be written
When. A number is written as a document writes it, one to three digits followed by , and groups of three: 1,000, 1,949,000JPY. Inside a cell , separates the members of a set, so as it stands the figure reads as more than one value. It used to be read that way without a word: <=1,000 in a column of numbers passed the check as <=1, and in a column of money it stopped at an E103 about 1 having no unit. Copying a document's figures as they stand lands here.
Fix. Take the commas out and write 1000; to group the digits, write 1_000. fix.text is the literal rewritten.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=0JPY <=5000JPY
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| <=1,000JPY | true |
| >1,000JPY | false |
E048
error — This type has no arithmetic
When. A type that is ordered but has no arithmetic is used with + - × ÷. There are three: date, temperature[℃]/temperature[℉], and sound[dB]. A ℃ has a displaced zero, so temp × 2 means nothing; a decibel is a logarithm, so adding two of them is not two sounds' worth; and a date is a calendar day, with no type to hold the result of subtracting one. All three appear in rules only as thresholds, so comparison and range are kept and the arithmetic is dropped.
Fix. Compare it against a threshold, or write it in a range. Where a difference or a multiple is itself the rule, take the computed value as an input, or look it up in a table. The days between two dates are counted on the calling side and passed in as a number or a duration.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : temperature[℃] range >=0℃ <=40℃
b(b) : temperature[℃] range >=0℃ <=40℃
outputs
x(x) : bool
derive gap(gap) : temperature[℃] = a - b range >=-40℃ <=40℃
table t1(t1)
policy unique
| gap | -> x |
| - | true |
Related codes: E103, E112, E115
E050
error — The machine section is not shaped right
When. The machine heading lacks a name or over <table>; the carry or initial line is missing, or the state's initial line is written twice; a line under machine is not carry, held, initial, final, never, once or always; or the rule has two machine sections.
Fix. The shape is machine <name>(<ascii>) over <table>, then carry <input> -> <output> and initial <state> (both required), and final <state>, …, never <state>, … after <state>, … and once <output> <cell> (all optional). To carry more values along, add a carry line and an initial <input> <value> line for each (E072).
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
Related codes: E051, E052, E053
E051
error — carry or over does not fit the declarations
When. The left of carry is not an input, the right is not an output, the two of the first carry line are not of one enum, the two of a later carry line are not of one type, the over table does not exist, or it does not decide the carried output. It is because the state is an enum of finitely many values that the claims about sequences of calls are decidable.
Fix. Declare the state as an input and an output of one enum, write carry <input> -> <output>, and name after over the table that has the carried output as an output column.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry zz -> nx
initial a
E052
error — A state the machine names does not fit
When. An initial, final or never line names a value the enum of the carried state does not have, or never names one state on both sides — which the first call that stays in it breaks, so it cannot be what anyone means.
Fix. Spell the value as the enum does. Write different states on the two sides of never.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
initial a
final d
E053
error — The once line does not fit the declarations
When. The output of once is not declared, is the carried state or another carried value, or the cell is -. A carried value is answered again on every call that keeps it, so counting it with once breaks on the first call that does. The cell is held to the output's type exactly as a table's cell is (E103 and the rest).
Fix. Name an output other than the carried state (once refund >0JPY). Say it about states with never … after ….
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
initial a
once nx c
E054
error — A rule that folds a sequence cannot be the step of a machine
When. A rule with a fold has a machine. Its answer depends on the whole sequence, so the inputs do not cut into finitely many columns and the transitions of one call cannot be counted — the reason diff refuses the same rule.
Fix. Read the sequence on the calling side and pass what it found (a count, a total) as an input. A rule that counts with count or sum can be the step of a machine.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
enum s(s) = p(p) | q(q)
inputs
st(st) : s
elements xs(xs)
w(w) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
nx(nx) : s
table j(j)
policy unique
| w | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
table m(m)
policy unique
| st | -> nx(nx) : s |
| p | q |
| q | q |
fold d over xs
a -> next
b -> take_first w
empty -> 0JPY
exhausted -> held
machine k(k) over m
carry st -> nx
initial p
E055
error — A scenario is not shaped right
When. A scenario is written in a rule without a machine; it has no name, or two share one; it has no rows; its heading has one column twice; it has a column for a carried input (the state, or another carried value); or it lacks a column for another input. The first call starts from the initial values and every later one from what the call before it answered, so a carried input has no column.
Fix. Write the table right under scenario <name>(<ascii>). The header is every input but the carried one, then ->, then every output. With no state carried from one call to the next, write the rows as examples.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| true | false |
| false | true |
scenario s(s)
| x | -> r |
| true | false |
Related codes: E107, E111, E050
E056
error — The held line does not fit the declarations
When. held names something that is not an input, a carried input (the state, or another carried value), or one name twice. held says which inputs one case passes with the same value on every call, from its first to its last — the amount of an order, the class of the person who applied — and the check then stops offering sequences of calls that change one of them as counterexamples.
Fix. Name inputs. The carried input is replaced by the answer of the call before on every call, so it cannot be held.
Smallest reproduction:
rule t(t) v1
enum s(s) = p(p) | q(q)
inputs
st(st) : s
x(x) : bool
outputs
nx(nx) : s
r(r) : bool
table m(m)
policy unique
| st | x | -> nx(nx) : s | r(r) : bool |
| p | true | q | true |
| p | false | p | false |
| q | - | q | false |
machine k(k) over m
carry st -> nx
held r
initial p
final q
Related codes: E050, E051, W126
E057
error — The declaration has no type
When. A line under inputs, outputs or elements, or a define or derive line, has a name and no type. Such a line used to be dropped in silence: the rule passed the check with one input fewer than its author wrote, and an example or a record that named it was then refused for a name nobody declared.
Fix. Write the type as <name>(<alias>) : <type> (: money[JPY, incl_tax], : <enum>, : bool). A numeric type needs a range too.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
b(b)
outputs
r(r) : bool
table j(j)
policy unique
| a | -> r(r) : bool |
| true | false |
| false | true |
Related codes: E011, E047, E012
E058
error — A declaration line cannot be read
When. A line that starts with a declaring word — define, derive, result, enum, group, table and the like — cannot be read as that declaration: it has no name, something stands before its =, and so on. Such a line used to be dropped with nothing said, and a derive line was not even dropped: the check never finished.
Fix. Rewrite the line in the shape the note gives (define bulk(bulk) : bool = order_total >= 30_000JPY). Every declaration's shape is in docs/reference.md.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
r(r) : bool
derive
table j(j)
policy unique
| a | -> r(r) : bool |
| true | false |
| false | true |
Related codes: E006, E057, E059, E005
E059
error — The expression cannot be read
When. An expression — on the right of =, of -> in a fold, and so on — cannot be read to its last token: nothing on the right of an operator (x +), a ( never closed, two values with no operator between them (amount tax), a sign stuck to a number (amount -100JPY). What could be read used to become the expression and the rest was dropped: amount tax passed the check as amount, and x + took its whole declaration with it.
Fix. Supply the missing operator or parenthesis. To subtract, leave a space after the sign, as in amount - 100JPY: a sign touching the digits makes a negative number.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=0JPY <=100JPY
b(b) : money[JPY] range >=0JPY <=100JPY
outputs
o(o) : money[JPY] round down(1JPY)
define s(s) : money[JPY] = a b
result o = s
Related codes: E058, E118, E115
E060
error — A step is not positive
When. The step of a type (rate[step 0%]), the rounding grid of an output (round up(0JPY)) or of a rounding call (down(x, 0JPY)) is zero or negative. A grid of zero passed the check and the generated code divided by it at run time (Python stopped with ZeroDivisionError); a step of zero was quietly read as a step of one.
Fix. Write a step greater than zero (round up(1JPY), rate[step 0.1%]).
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=0JPY <=100JPY
outputs
o(o) : money[JPY] round up(0JPY)
result o = a
Related codes: E104, E114, E106
E061
error — The range is empty
When. No value lies in a range: the lower end is past the upper one (range >=10JPY <=0JPY), or the two are equal and one of them is > or <. Such a range used to pass with nothing said: the completeness proof answered "complete" over nothing, and the generated code's entry refused every call.
Fix. Correct the end that was mistyped (range >=0JPY <=10JPY).
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=10JPY <=0JPY
outputs
o(o) : money[JPY] round down(1JPY)
result o = a
Related codes: E112, E103, E060
E062
error — There is no such date
When. A literal has the shape of a date (2026-04-01) but no such day: the month is outside 1 to 12, or the day is past the end of its month (2026-02-30, 2026-01-99). Such a date used to be read by its shape alone: the checker counted on from the first of the month (January 99th was April 9th), the generated Python stopped at run time, and JavaScript's Date would have rolled it over quietly, so the languages disagreed.
Fix. Write a day that exists. For the end of a month, write its last day (2026-02-28).
Smallest reproduction:
rule t(t) v1
inputs
d(d) : date range >=2026-01-01 <=2026-12-31
outputs
r(r) : bool
table j(j)
policy unique
| d | -> r(r) : bool |
| <=2026-02-30 | true |
| >2026-02-30 | false |
E063
error — The cell cannot be read
When. A cell of a table, of the examples or of a sequence holds something a cell cannot: words after a comparison (<=0JPY + false), a word after a value (false 0JPY), a word that is no value (+), a header column with no name. What could be read used to become the cell and the rest was dropped, and a cell that could not be read at all was dropped whole, so every cell after it moved one column to the left.
Fix. A cell holds one value, -, none, a comparison, a set of values separated by commas, not: with a set, or starts_with with a string. Two conditions on one column are two comparisons side by side (>=1 <10).
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=0JPY <=1000JPY
outputs
r(r) : bool
table j(j)
policy unique
| a | -> r(r) : bool |
| <=100JPY + 1JPY | true |
| >100JPY | false |
Related codes: E008, E010, E014, E064
E064
error — The row does not fit the header
When. A row of a table, of a sequence, of a scenario or of the examples has a different number of cells from the header's columns, or words follow its last | — the closing bar was left off. The last cell of such a row used to be dropped with nothing said, and a row whose cells did not match was read as it stood; the reference evaluator and the generated code then read the row two different ways and answered differently. In the examples, a header without a column for an output, or a row short of an expected value, is E111, and a header without the sequence's column is E025: each names the column first.
Fix. Write one cell per column and close the row with |. A column that takes any value gets -.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
r(r) : bool
table j(j)
policy unique
| a | -> r(r) : bool |
| true |
| false | true |
Related codes: E063, E008, E111
E065
error — range from koyomi is misshapen or misplaced
When. What follows range from is not koyomi "<file>" date <name of a date>, or it is written on an input that is not a date, or on an output, a derive or a field of an element. The days a koyomi date comes to can only be the range of a date the caller passes.
Fix. Write range from koyomi "<file>" date <name of a date> on a date input. The file is a path from the rule's directory.
Smallest reproduction:
rule t(t) v1
inputs
d(d) : date range from koyomi "terms.cal"
outputs
r(r) : bool
table j(j)
policy unique
| d | -> r(r) : bool |
| - | true |
E066
error — An enums file, or an import enum from one, is misshapen
When. A file headed enums holds a line other than description, import, enum, group and prerequisites (a table, inputs, outputs, a definition, an import enum, …); or import enum <enum> from "<file>" names a file that is not an enums file, or one that does not pass check. An enums file holds the enums rules share: it decides nothing, and no rule's function is generated from it.
Fix. Write the tables and the inputs in a rule that reads the enum with import enum … from. Start the file it reads with enums <name> v<version>, and make it pass check.
Smallest reproduction:
enums kinds v1
enum kind = chilled | ambient
table fee
policy unique
| kind | -> fee : number |
| - | 1 |
E069
error — An ordered enum, a comparison on one, or a display name is misshapen
When. A cell that compares (<v, >=v) is written in a column of an enum whose values are separated by |, and so have no order; or two values of such an enum are compared with < and the like, passed to max or min, or held to a range on an input. Or one enum separates its values with both < and |. Or a range lets no value in, or an enum field of an element is given a range, or a range is written on the line of a max or a min of an enum column. Or a value's display name is empty or holds a control character, or two values of one enum have the same display name. The values of an enum separated by < are ordered as written, and a comparison takes the values before (after) the one it is against.
Fix. If the order the values are declared in means something, separate them with < (enum tier = basic < silver < gold); if not, write values or sets. Use one separator throughout, and give each value a display name that is not empty and differs from the others'.
Smallest reproduction:
rule member_fee v1
enum tier = basic | silver | gold
inputs
member : tier
outputs
fee : money[USD] round down(1USD)
table fees
policy unique
| member | -> fee |
| >=silver | 0USD |
| basic | 5USD |
E071
error — The prerequisites are misshapen
When. In a prerequisites block: over names something that is not an enum; a name is not a value of the enum; a value is written in two places (on start and on a line of its own, on two lines, or twice on one line); a value is neither on start nor on a line of its own; there is no start line; or a line cannot be read. Or the table ordered by names is not there, is declared below the block, or does not test one column of the enum and write, for each value, one value of an ordered enum or one number; or ordered by is written in an enums file. Every value needs nothing (start), has a line of its own saying what it needs, or is one no case can take (requires nothing "<reason>"), so a value added without its prerequisites stops here.
Fix. Write each value in one place: a value that needs nothing on start, one that needs something on a <value> requires <prerequisite>, … line, and one no case can take as <value> requires nothing "<reason>". Declare the table of ordered by above the block, make it test the enum's column alone, and write one value of an ordered enum or one number in each row.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| - | true |
prerequisites p(p) over k
start a
b requires a
Related codes: E136, E137, E138
E072
error — A carried value is not written right
When. A carry line after the first carries a number whose range lacks an end, or a date, a string or an optional value; a value is carried twice; a carried value has no initial <input> <value> line, or two, or one whose value does not fit its type or range; an initial <input> <value> line names the state or an input that is not carried; an always line is not shaped right; or an always line names something that is not a carried value, or the state itself. It is because a carried number has both ends of a range that the values a case can hold are finitely many, and the claims about sequences of calls decidable.
Fix. Give a carried number a range with both ends, as in range >=0 <=7, and every carried value the value it starts at with initial <input> <value>. An always line names the input on the left of a later carry line with a cell, joined by and, and the states it holds in after when (always days_left >=1 <=7 when in_grace). What is said of the state is said with never and final.
Smallest reproduction:
rule t(t) v1
enum s(s) = run(run) | done(done)
inputs
st(st) : s
n(n) : number range >=0 <=3
outputs
nx(nx) : s
nn(nn) : number round down(1)
define more(more) : number = n + 1
table m(m)
policy first
| st | n | -> nx(nx) : s | nn(nn) : number |
| run | <3 | run | more |
| - | - | done | n |
machine k(k) over m
carry st -> nx
carry n -> nn
initial run
Related codes: E050, E051, E135
E075
error — A word is both a value of an enum and a name
When. A word in an output cell of a table, or in an answer of a fold, is a value of the column's (the answer's) enum, and also the name of an input, an output, a field of an element, a derive, a definition or a table's output whose type is that enum. It reads either way, and which one is meant is not decided. Where the name's type is another, the word is read as the column's value (an output gold that is a number does not stop gold in a column of the enum from being the enum's value). An expected value in an example or a scenario is always a value, since nothing else can be written there.
Fix. Rename either the enum's value or the name.
Smallest reproduction:
rule carry_stage v1
enum stage = fresh | previous | done
inputs
previous : stage
close : bool
outputs
next_stage : stage
table decide
policy unique
| close | -> next_stage : stage |
| true | done |
| false | previous |
E101
error — Completeness gap: some input matches no row
When. The union of the rows does not cover the declared input space. Completeness cannot be waived and is always required. A concrete input that matches no row is always attached. Where a column is a value the rule computes (a derive, a define, the output of a table above), the example gives its value, and the input that produces it too, once the reference evaluator has confirmed it. That input is worked out from the expression where a derive only adds, subtracts and multiplies by constants, and otherwise (a min or a rounding, a define, a table above) looked for among every combination of the inputs when there are no more than 16,384. A derive's values can fall finer than the units a cell is written in (amount * 10% comes in tenths), and they are read on that step. No row is asked for where no input reaches: on the column of a derive or of a define that computes a number, past what it can actually come to (its expression computed over the inputs' ranges and the values the tables above produce, and for a derive over the constraint lines too); on a column a table above decides, at values of the other columns that the rows writing its value never let come with it; on the column of a boolean define of one comparison, at the truth value it never takes over the inputs' ranges (made <= 2027-03-31 with made declared only up to that day is never false). An optional input (T?) takes the absent value too, and a row is asked for it; an optional number, date or truth value is asked for the whole of its declared range besides. An optional column a table above decides takes the absent value where a row above writes none or passes on a name that may have none, and a row is asked for it there.
Fix. Add a row that matches the witness. If a new enum value caused it, add a row for that value or a - row that catches everything. If the value needs no row of its own, mark it default in the enum declaration.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| a | true |
| b | false |
Related codes: E102, E105, W111
E102
error — Unreachable row: the row never matches
When. No input matches the row. There are three forms, told apart in the wording. In the first, every input the row would take is already taken by an earlier row, or by a row that takes precedence through overrides. In the second, the row names a value that the upstream table never produces. In the third, the row asks a derived column, or the column of a define that computes a number, only for values outside what it can actually reach, the part inside (if any) being taken first by earlier rows; so does a row that asks a column a table above decides and a derived column for values the derive never comes to while the rows that write that value fire, and a row that asks a boolean define of one comparison for the truth value it never takes over the inputs' ranges. What a value can reach is its expression computed over the inputs' ranges and the values the tables above produce (for a derive, over the constraint lines too), not the range written on it.
Fix. If the row is the newer intent, move it above the row that covers it. If it is dead, delete it. If it names a value the upstream never emits, either add a row upstream that emits it, or delete this row. If it asks a derive or a define for values it never reaches, rewrite the condition within the values it can come to, or delete the row; widening a derive's range does not change those values. If it asks a boolean define for the truth value it never takes, rewrite the condition to the value it always takes, or delete the row.
Smallest reproduction:
rule t(t) v1
inputs
w(w) : mass[g] range >=0g <=10kg
outputs
r(r) : bool
table j(j)
policy first
| w | -> r(r) : bool |
| - | true |
| <=1000g | false |
Smallest reproduction of the third form (a row outside what a derive can reach):
rule t(t) v1
inputs
amount(a) : money[JPY, incl_tax] range >=1JPY <=100JPY
limit(l) : money[JPY, incl_tax] range >=0JPY <=100JPY
outputs
r(r) : bool
derive excess(x) : money[JPY, incl_tax] = amount - limit range >=-1000JPY <=1000JPY
table j(j)
policy unique
| excess | -> r(r) : bool |
| <=0JPY | false |
| >0JPY <=100JPY | true |
| >100JPY | true |
Smallest reproduction of the third form (a row outside what a define can reach):
rule t(t) v1
inputs
score(p) : number range >=0 <=200
outputs
r(r) : bool
define share(s) : rate = score / 200
table j(j)
policy unique
| share | -> r(r) : bool |
| <50% | false |
| >=50% <=100% | true |
| >100% | true |
Smallest reproduction of the third form (a row asking a derive for values it never comes to while a table above writes the value the row asks for):
rule t(t) v1
enum size(z) = small(small) | large(large)
inputs
amount(a) : money[JPY, incl_tax] range >=1JPY <=100JPY
limit(l) : money[JPY, incl_tax] range >=0JPY <=100JPY
outputs
r(r) : bool
derive excess(x) : money[JPY, incl_tax] = amount - limit range >=-100JPY <=100JPY
table k(k)
policy unique
| amount | -> band(c) : size |
| <=50JPY | small |
| >50JPY | large |
table j(j)
policy unique
| band | excess | -> r(r) : bool |
| small | <=50JPY | false |
| small | >50JPY | true |
| large | - | true |
Smallest reproduction of the third form (a row asking a boolean define for the truth value it never takes):
rule t(t) v1
inputs
made(d) : date range >=2024-04-01 <=2026-12-31
outputs
r(r) : bool
define in_period(p) : bool = made <= 2027-03-31
table j(j)
policy unique
| in_period | -> r(r) : bool |
| true | true |
| false | false |
Related codes: E101, W105, W110, E112
E103
error — Unit mismatch: values of different types are being mixed
When. An expression or a cell adds or compares values whose unit, currency or tax flag differ. money[JPY, incl_tax] and money[JPY, excl_tax] are different types too. It is also a step that cannot be read (rate[step 1g]), and a rate input that declares no step: either way, what the integer passed at runtime counts is not settled. So is a money type whose tax is a word other than incl_tax and excl_tax (money[JPY, foo]). And it is a value or a group written in a column whose enum does not have it, an output cell included, and a group that mixes the values of two enums. And it is a number in an expression that is no value of the unit it is written in: a bare decimal (amount * 0.4; a number with no unit is a number, which is whole) and a fraction of a money or quantity unit (0.5JPY). And it is the heading of a table, a clause or examples that writes a type other than the one its name already has: the declared type of an output, or the type a table or clause above decides the name in. And it is none, or a name that may have no value, written under a column that is not optional: an output, a column a table decides, an expected value. And it is a name that may have no value worked with in an expression (quoted * 2): such a value is tested in a table's cells, or handed on to an optional column as it is, and nothing else.
Fix. Move one side into a table. "A surcharge that depends on weight" is table weight_surcharge | weight | -> surcharge : money[JPY, incl_tax] |. A tax conversion is also written as a table, never as a formula. A step is written in the unit of its type (a rate input takes rate[step 1%] or rate[step 0.1%]). A tax is incl_tax with tax and excl_tax without. A rate is written with % (0.4 is 40%, which fix.text gives). Where a heading's type is not its name's, decide which is right and correct the other; a heading may also go without a type, and is then read in the name's. Where a column is to answer that there is no value, make its type T?: the declaration under outputs for an output, the heading for a column a table decides; otherwise write a value in the place of none.
Smallest reproduction:
rule t(t) v1
inputs
w(w) : mass[g] range >=0g <=10kg
p(p) : money[JPY, incl_tax] range >=0JPY <=10_000JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| w | -> r(r) : money[JPY, incl_tax] |
| - | 100JPY |
result r = p + w
A table's heading that writes a type other than the output's:
rule t(t) v1
inputs
w(w) : mass[g] range >=0g <=10kg
outputs
r(r) : money[JPY, excl_tax] round down(1JPY)
table j(j)
policy unique
| w | -> r(r) : money[JPY, incl_tax] |
| - | 100JPY |
none under an output that is not optional:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| x | -> r(r) : money[JPY, incl_tax] |
| true | 100JPY |
| false | none |
E104
error — A numeric output declares no rounding
When. A quantity, money or rate output has no round, an optional one (money[JPY]?) included. Unless the fraction is declared, the generated code settles it silently. When the expression can produce a fraction, the message shows in yen how far the choice moves the answer.
Fix. Add rounding to the output declaration, e.g. round up(10JPY). There are five directions (up, down, half_up, half_down, half_even), pinned down for negative values as well.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : money[JPY, incl_tax]
table j(j)
policy unique
| x | -> r(r) : money[JPY, incl_tax] |
| true | 100JPY |
| false | 200JPY |
E105
error — Overlapping rows: the same input matches two or more rows
When. In a policy unique table, an input matching both rows was actually constructed. An overlap that could not be constructed falls to W114 instead. A pair that meets only on a combination the tables above never produce together, or only where no input reaches — past what a define comes to, at the truth value a boolean define never takes, where the rows of a table above leave a derive no values — is not reported: the completeness check asks no row there, and E102 calls a row that asks only for such points dead. On an optional column a comparison, a literal and a set of values match only a value that is there, while - and not: match the absent value too; so a none row meets a - row, and a not: 5 row, on the absent value.
Fix. If the outputs differ, decide which is right and fix the rows; to let the order decide, declare policy first. If even the outputs agree, delete one of the rows. Where two tables or clauses that define one output overlap, write overrides <the other> on the one that wins: after policy on a table, as a line under a clause. The overlap is refused even when the two answer the same, since an edit tomorrow may change one of them and not the other.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b)
inputs
x(x) : k
y(y) : bool
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| x | y | -> r(r) : money[JPY, incl_tax] |
| a | - | 100JPY |
| - | true | 200JPY |
| b | false | 300JPY |
Related codes: W105, W114, E102
E106
error — An output literal is not on the rounding grid
When. A literal in an output cell is not a multiple of the declared rounding grid. This is where a mistyped digit — 1451JPY in a table rounded up(10JPY) — is stopped.
Fix. Put the literal on the grid (1451JPY becomes 1450JPY or 1460JPY). If the amount really is right, change the grid instead.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : money[JPY, incl_tax] round up(10JPY)
table j(j)
policy unique
| x | -> r(r) : money[JPY, incl_tax] |
| true | 1451JPY |
| false | 1000JPY |
Related codes: E104
E107
error — An example does not match
When. Running a row of examples through the reference evaluator gives something other than the value written. Which row of which table fired is attached. examples is an executable specification. An expected none under an optional output expects no value.
Fix. If the table is right, fix the expected value. If the expected value is the business truth, fix the row that fired. Which one to fix is settled by the source: the written rule, the spreadsheet, or the legacy implementation.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| x | -> r(r) : money[JPY, incl_tax] |
| true | 100JPY |
| false | 200JPY |
examples
| x | -> r |
| true | 200JPY |
E108
error — Cannot prove an intermediate value fits in int64
When. The reachable interval computed from the declared ranges and steps exceeds int64. With a rate step of 1%, the stored integer is 100 times the value. So is a rule whose random inputs (random) have more than 2^62 points together: a chance is worked out by counting the points, and every language holds the count in an int64.
Fix. Narrow the input ranges, or insert one rounding step along the way. Where to round is a business decision that moves yen, so the tool does not decide it. Where the random inputs have too many points, narrow their ranges.
Smallest reproduction:
rule t(t) v1
inputs
p(p) : money[JPY, incl_tax] range >=0JPY <=100_000_000_000_000_000JPY
q(q) : rate[step 1%] range >=0% <=100%
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
define off(off) : money[JPY, incl_tax] = p * q
table j(j)
policy unique
| off | -> r(r) : money[JPY, incl_tax] |
| - | 0JPY |
The points of the random inputs:
rule t(t) v1
inputs
a(a) : number range >=0 <=9999999999 random
b(b) : number range >=0 <=9999999999 random
outputs
r(r) : bool
table j(j)
policy unique
| a | b | -> r(r) : bool |
| <5 | - | true |
| >=5 | - | false |
E109
error — The check exceeded its budget, so completeness could not be proven
When. The region check visited more nodes than --budget allows. Failing to prove something is never green here, so this is an error and not a warning.
Fix. Split the table to reduce the number of columns, or raise --budget (both rulec check and ritsu check take it). The cost is the product of the columns, so chaining tables in a linear pipeline is cheaper than piling columns into one table.
Smallest reproduction (with --budget 1):
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| a | true |
| b | false |
| c | true |
E110
error — A column has a type the check cannot handle
When. The column's type cannot be lowered into the region IR. Seeing this is a bug in rulec itself. It is the internal breakwater that stops a table from being skipped silently, put in after the same accident happened twice, with dates and with optional.
Fix. Change the column to a type the check handles today (bool, enum, quantity, money, rate, date, optional). Then report it: this check exists because stopping is better than passing silently.
Smallest reproduction:
rule t(t) v1
inputs
s(s) : string?
outputs
r(r) : bool
table j(j)
policy unique
| s | -> r(r) : bool |
| starts_with "a" | true |
E111
error — The examples have no column for an output
When. examples writes only some of the declared outputs. Agreement across the implementations stays green when they all carry the same mistake, so only a human-written expectation can break it. Rounding for multiple outputs really did go missing in every one at once.
Fix. Add the missing output column to examples. With two or more outputs, writing -> before the later columns is optional.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
outputs
ok(ok) : bool
fee(fee) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| x | -> ok(ok) : bool | fee(fee) : money[JPY, incl_tax] |
| true | true | 100JPY |
| false | false | 0JPY |
examples
| x | -> ok |
| true | true |
Related codes: E107
E112
error — The range of a derived value does not contain the values it can reach
When. The interval computed from the input ranges falls outside the range declared on the derived value. With too narrow a range, the completeness check answers "complete" without ever looking at values that really occur. So does a range written on a max or a min that leaves out part of its column's range or its empty value, since it answers one of those.
Fix. Widen the range to the reachable interval the message states (it is written out, e.g. range >=-1_100_000JPY <=1_000_000JPY). Widening past what is reachable costs nothing: the check sifts the infeasible part out.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY, incl_tax] range >=0JPY <=1_000_000JPY
b(b) : money[JPY, incl_tax] range >=0JPY <=1_000_000JPY
outputs
r(r) : bool
derive gap(gap) : money[JPY, incl_tax] = a - b range >=0JPY <=1_000_000JPY
table j(j)
policy unique
| gap | -> r(r) : bool |
| <=0JPY | false |
| >0JPY | true |
E113
error — The condition of a boolean definition is neither of the two allowed forms
When. The condition of define … : bool is neither one input or derived value compared with a constant, nor a comparison of two values whose difference cannot be subtracted (two dates, say). Comparing two numbers directly is the usual case.
Fix. Declare the difference as a derived value and compare that against a constant. define bigger : bool = a >= b becomes derive gap(gap) : money[JPY, incl_tax] = a - b range … and the cell >=0JPY. The analysis is exact that way.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY, incl_tax] range >=0JPY <=1_000_000JPY
b(b) : money[JPY, incl_tax] range >=0JPY <=1_000_000JPY
outputs
r(r) : bool
define bigger(bigger) : bool = a >= b
table j(j)
policy unique
| bigger | -> r(r) : bool |
| true | true |
| false | false |
E114
error — A value does not sit on the declared step
When. A value that is not a whole number of the declared step is written in the column, such as 0.5% where the type says rate[step 1%]. At runtime the value is one integer count of that step, so this one has no representation. The same holds for the rounding grid of a rate output that is not a whole number of the step the output declares (rate[step 1%] with round down(0.5%)): every answer sits on the grid and is handed over as a whole number of the step, so some answers could not be written.
Fix. Write a value on the step, or declare a finer step (rate[step 0.1%]). Quietly moving it to the nearest step would make the boundary on the page differ from the boundary in the generated code. For a rounding grid, declare the step the rounding needs, or round to a whole number of the step.
Smallest reproduction:
rule t(t) v1
inputs
r(r) : rate[step 1%] range >=0% <=100%
outputs
o(o) : bool
table j(j)
policy first
| r | -> o(o) : bool |
| <=0.5% | true |
| - | false |
E115
error — The divisor is not a positive constant
When. The right of ÷ is neither a positive whole constant nor a positive constant amount or quantity in the same unit: a variable, zero, a negative number or a fraction. A division that is the outermost operation of a rounding function's argument may divide by a value (half_up(total / units, 1JPY); E132 sees that the divisor is never zero). The divisor n of mod(x, n) is this too when it is not a positive whole constant.
Fix. To divide by a value, put the division inside a rounding function, with the grid and the way to round (half_up(total / units, 1JPY)). If the divisor is business data that can come as a rate, take it as a rate input, or look the constant up in a table. Without a statically known step the generated code falls back on the language's own division, and Python rounding toward -inf and Go toward zero disagree.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=100
d(d) : number range >=1 <=100
outputs
o(o) : bool
define r(r) : number = n ÷ d
table j(j)
policy first
| r | -> o(o) : bool |
| - | true |
E116
error — A row's amount is not in the copy it cites, or is under another heading there
When. The output value of a row is nowhere in the copy the row or its table cites with @source table1. Where the copy has a heading that says exactly a word of the row's cells (Kanto), only the row and the column under that heading are searched: the amount of the next row is somewhere in the copy too, and a search of the whole table would let the two be mixed up. Only amounts are compared: a threshold is rewritten as it is transcribed (up to 1,949,000JPY becomes <=1949000JPY) and an amount is not. A copy's 5/1,000 (or, in Japanese, 1,000分の5) reads as 0.5%. The copy is what rulec source fetch took out of the document; check does not read the document itself.
Fix. Reread the copy and correct the amount. When it is said to be under another heading, the rows were mixed up. For a mistyped digit W120 usually comes with it, naming the value left unused. If the value came from somewhere else — a later notice, a correction, an answer from a person — take the citation off this row and write where it came from in a comment at the end of it, which rulec doc shows on the page for people.
Smallest reproduction:
rule t(t) v1
source tariff = file "tariff.md" sha256:583beb1ff0102e2e
table1 sha256:07423c36f9e9259f
inputs
a(a) : bool
outputs
x(x) : money[JPY] round down(1JPY)
table t1(t1) @tariff table1
policy unique
| a | -> x |
| true | 990JPY |
| false | 890JPY |
With tariff.md beside it:
With tariff.md.fragments/table1.tsv beside it:
Related codes: W120, E038, E107
E117
error — A share without what a share needs
When. The three of allocate(<amount>, <running total>, <whole>) are not the shape a share needs. All three are names with declared ranges, the amount and the running total cannot be negative, the whole is positive, and a constraint says the running total never passes the whole.
Fix. Write what is missing. Without constraint <running total> <= <whole> a share can exceed the amount being handed out and the lines no longer add up to the total. Below zero the targets disagree about which way to round.
Smallest reproduction:
rule t(t) v1
inputs
off(off) : money[JPY] range >=0JPY <=1000JPY
upto(upto) : money[JPY] range >=0JPY <=1000JPY
base(base) : money[JPY] range >=1JPY <=1000JPY
outputs
o(o) : money[JPY] round down(1JPY)
derive share(share) : money[JPY] = allocate(off, upto, base) range >=0JPY <=1000JPY
result o = share
E118
error — The call is not written correctly
When. A call to a function that does not exist, or with the wrong number of arguments. The calls are min(a, b), max(a, b), allocate(<amount>, <running total>, <whole>), mod(x, n), isqrt(x) and the five rounding modes (down(x, 1JPY) and the rest). A rounding grid that is not a constant is this too.
Fix. Check the spelling and the count. A spare argument is dropped on the floor and a missing one leaves no answer — both used to pass unnoticed, and only the generator ran out of cases.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=0 <=100
d(d) : number range >=1 <=100
outputs
o(o) : number round down(1)
define r(r) : number = min(n, d, n)
result o = r
E120
error — A from does not fit the input's type
When. What a from yields does not fit the input that takes it. any and all yield a bool, count yields a number, sum a number (a number, money, a quantity or a rate), and max and min a number or a date. It is also this code when the type at the end of the path does not fit the input — a field the contract calls a string taken by a number input — when any, all, count, sum, max or min would walk something that is not a collection, and when the value of a where does not fit the field; when the field of an element of reads does not fit the input (sum adds whole numbers, and a fraction cannot be added up exactly); when a sequence is taken from something that is not a collection; and when a field of an element does not fit the field it is read into. A contract says how a value travels: an enum and a date arrive as strings, and money and a quantity as whole numbers in the unit the rule declares.
Fix. Correct the type or the from. A count is taken by a number input with a range, whether the elements passed by a bool, and the value itself by from <shape>.<field>. A where value carries no unit: a contract has none, and comparing a scaled number with a raw one is the thing this must not do quietly.
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : bool from count order.lines
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"}}}}}}}}
Related codes: E121, W122, E103
E121
error — The contract has no such path
When. A from path is not in the contract of the shape it starts at: the first word is not the name of a shape, a field along the way is not there, or the field a where tests or an of reads is not a field of an element. The same goes for the path of a sequence taken with elements … from, and for the path of each field of its elements, shown after [] inside the element (order.lines[].qty). The message says how far it resolved and which names were there. A path that goes on through a collection — an array in a JSON Schema, a repeated field in a .proto — is this code too: a path reads one value, and each element has its own, so which one the input takes is not said. The message names every collection along the path. The contract may be a .proto or a JSON Schema, is read on every check like import proto, and carries no pin.
Fix. Correct the spelling, or rewrite the path if the contract moved. This firing is the point: a contract that renamed a field goes unnoticed in hand-written glue until it runs, and stops the build here. Where the path goes through a collection, read the collection instead: test a field of its elements with from any <collection> where <field> = <value> (or all, count), add up or compare a number with sum, max or min, or walk the elements with elements <name> from <collection>. Where the collection always holds one element, make it one object in the contract (a field that is not repeated).
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : bool from order.nope
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"}}}}}}}}
A path that goes on through a collection:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : string from order.lines.category
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"}}}}}}}}
Related codes: E120, W122, E032
E119
error — A row's boundary falls on the other side from the copy it cites
When. A threshold of a row that cites puts its boundary value on the other side from the copy. A threshold is rewritten as it is transcribed (up to 1,949,000JPY becomes <=1949000JPY) so the text cannot be compared; what is compared is which of the two bands the boundary value falls in. The copy's up to 60cm and over 60cm both put 60cm in the band below, and so do <=60cm and >60cm. A number the copy bounds with no word (18 to 20, 60-80), with the word in another column (a Japanese insurance premium table puts 円以上, "yen or more", in the heading over its own column), or with words on both sides, is left alone.
Fix. Reread the copy and correct it. fix.text is this cell with that one boundary's side swapped and nothing else: the direction is the table's geometry, not the copy's to decide, so only < and <= are exchanged. One boundary mistranscribed is reported on both of the rows that share it. If the boundary came from somewhere else — a later notice, a proviso in the text — take the citation off this row and say in a comment at the end of it where it came from.
Smallest reproduction:
rule t(t) v1
source sizes = file "sizes.md" sha256:8a2a4c38c68dca29
table1 sha256:34cc5df35a840201
inputs
a(a) : length[cm] range >=1cm <=80cm
outputs
x(x) : money[JPY] round up(10JPY)
table t1(t1) @sizes table1
policy unique
| a | -> x |
| <60cm | 1410JPY |
| >=60cm <=80cm | 1710JPY |
With sizes.md beside it:
With sizes.md.fragments/table1.tsv beside it:
Related codes: E116, W120, E105
W122
warning — No input is projected from that shape
When. A shape is declared and no input or sequence says from <that name>.…. The contract is read and holds nothing.
Fix. Use it or delete it. A contract that is only read makes the next reader believe this rule is held to it; what is held to it is the inputs that say from.
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : bool
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"}}}}}}}}
E122
error — The contract lets through a value the rule refuses
When. A value read with from can pass the contract's validation and still be refused by the input's declaration. What is compared: the range of a number (Protovalidate's gte, lte and the rest; JSON Schema's minimum and maximum), the length of a collection (min_items and max_items; minItems and maxItems), the values of an enum (string.in; enum — held to the values inside its range when an ordered enum input has one), and JSON Schema's required. A from sum is held by how far its total reaches — the values of the field times how many elements there may be — and a from max or from min by the values of the field. A sequence taken with elements … from has each field of its elements held as an input is, its length held to the cap its counts put on it, and its totals to the upper end of each sum over it. A proto3 number field with no rule arrives as 0 when it is left unset, so an input that does not take 0 stops here. A message field that is not required, and an optional field, may be left unset, and the value under it then arrives as its default (0, "", no elements) with no rule applied; a date read from a .proto string is held to whether "" passes. A condition across fields — a CEL expression, a oneof, JSON Schema's allOf, anyOf, oneOf, not and if — is read too where it narrows one field, and a JSON Schema type that has null in it is this code when the input is not optional. A rule that cannot be read, such as CEL with a remainder or a string function, is read as not there: the contract is then read wider than it is, so this may speak where it did not need to, and never stays quiet where it should have spoken.
Fix. Which side to change is a person's decision. If the value cannot occur, narrow the contract: fix.text is the annotation to write there (narrow_contract). If it can, widen the rule's range or add the value to the enum, and decide what it answers. Some preconditions cannot be written in a contract, such as a floor on how many elements a where picks out; fix.kind is then none. When the value read may be missing, make the input T?: a missing value is then read as none.
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : number range >=0 <=5 from count order.lines
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","maxItems":10,"items":{"type":"object"}}},"required":["lines"]}}}
Related codes: W123, E123, E121, E032
W123
warning — A row is reached only by values the contract does not let through
When. A cell of a row tests an input read with from, and nothing the cell accepts passes the contract's validation. Only what passed the contract arrives, so no request or message reaches the row. Only a column of the input itself is compared; a column derived from it is not.
Fix. If the contract will not widen, delete the row and bring the input's range in line with the contract. If the row is kept for a widening that is planned, leave it: check --diff-base in CI reports only the ones that are new.
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
a(a) : number range >=0 <=20 from count order.lines
outputs
x(x) : bool
table t1(t1)
policy unique
| a | -> x |
| <=10 | true |
| >10 | false |
With order.json beside it:
{"$defs":{"Order":{"type":"object","properties":{"lines":{"type":"array","maxItems":10,"items":{"type":"object"}}},"required":["lines"]}}}
Related codes: E122, W124, E102, W111
E123
error — The contract lets through a combination the rule's constraint refuses
When. Both sides of a constraint are inputs read with from from the same shape, and some request passes the contract's validation with both values inside the inputs' ranges and the constraint broken. The conditions the contract places across its fields — CEL on a .proto message, a oneof, JSON Schema's combinators — are read, and what is asked is whether a breaking combination survives them. When one does, its values are the example. A rule that cannot be read is read as not there, so nothing is missed.
Fix. Which side to change is a person's decision. If the combination cannot occur, promise it in the contract: for a .proto, fix.text is the (buf.validate.message).cel to write on the message (narrow_contract). JSON Schema has no way to compare the values of two fields, so there fix.kind is none. If it can occur, take the constraint off and decide in the tables what the rule answers for it.
Smallest reproduction:
rule t(t) v1
shape quote(q) = proto "quote.proto" shop.v1.Quote
inputs
minimum(min_g) : mass[g] range >=1g <=30kg from quote.min_g
maximum(max_g) : mass[g] range >=1g <=30kg from quote.max_g
constraint minimum <= maximum
outputs
x(x) : bool
table t1(t1)
policy unique
| minimum | -> x |
| - | true |
With quote.proto beside it:
syntax = "proto3";
package shop.v1;
message Quote {
option (buf.validate.message).cel = {id: "express_cap", expression: "!this.express || this.weight_g <= 5000"};
int64 min_g = 1 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
int64 max_g = 2 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
int64 weight_g = 3 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
bool express = 4;
}
Related codes: E122, W124, E018
W124
warning — A row is reached only by a combination the contract does not let through
When. A row's cells test two or more inputs read with from from the same shape, each cell alone asks for values the contract lets through, and under the conditions the contract places across its fields no combination of them passes: a row asking for a minimum above 20kg and a maximum of at most 10kg under the CEL this.min <= this.max, or a row asking for two members of one oneof to be both non-zero. The cells on other columns are left out, so this may call a row reachable that is not, never the other way round.
Fix. If the contract will not change, delete the row. If the row is kept for a change that is planned, leave it: check --diff-base in CI reports only the ones that are new.
Smallest reproduction:
rule t(t) v1
shape quote(q) = proto "quote.proto" shop.v1.Quote
inputs
weight(weight) : mass[g] range >=1g <=30kg from quote.weight_g
express(express) : bool from quote.express
outputs
fee(fee) : money[JPY] round up(10JPY)
table fees(fees)
policy first
| express | weight | -> fee |
| true | >5kg | 3000JPY |
| true | - | 1500JPY |
| false | - | 800JPY |
With quote.proto beside it:
syntax = "proto3";
package shop.v1;
message Quote {
option (buf.validate.message).cel = {id: "express_cap", expression: "!this.express || this.weight_g <= 5000"};
int64 min_g = 1 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
int64 max_g = 2 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
int64 weight_g = 3 [(buf.validate.field).int64 = {gte: 1, lte: 30000}];
bool express = 4;
}
Related codes: W123, E123, E102
E124
error — A final state has a way out
When. A call moves a case out of a state the final line names: a case that had ended is set going again. The shortest sequence of calls to that state, and the call that leaves it, come with it as witness.trace.
Fix. Keep the state where it is on that row, or take it off the final line. Which of the two is right is the business's to say.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
initial a
final b, c
E125
error — A case can reach a state it can never finish from
When. A state a case can reach from initial is not final, and no final state can be reached from it: the "option to complete" of workflow-net soundness is broken. The shortest sequence of calls that gets there comes with it. A case whose carried number has left its range, so that no further call can take it, is E134's.
Fix. Add a row that moves on to a final state, or, if a case rightly ends there, add the state to final.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c) | d(d)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | d |
| b | fwd | c |
| b | rev | a |
| c | - | c |
| d | - | d |
machine k(k) over m
carry st -> nx
initial a
final c
Related codes: E124, W125, E134
E126
error — A never line is broken
When. For never A after B, some sequence of calls from initial reaches A after it has been in B. The shortest one comes with it, and every call in it is an input the rule takes and answers.
Fix. Correct the rows that decide where a case goes, or drop the never line if the claim is wrong as business.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
initial a
final c
never c after b
E127
error — A once line is broken
When. For once <output> <cell>, one case has two calls whose output the cell accepts: a refund paid twice, a point granted twice. The shortest such sequence comes with it.
Fix. Add the row that cuts the sequence short before the second call (a case that has been refunded does not go back, say), or drop the once line if it is wrong.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
f(f) : bool
table m(m)
policy unique
| st | ev | -> nx(nx) : s | f(f) : bool |
| a | fwd | b | false |
| a | rev | a | false |
| b | fwd | c | false |
| b | rev | a | true |
| c | - | c | false |
machine k(k) over m
carry st -> nx
initial a
final c
once f true
E128
error — The machine's claims could not be checked
When. The inputs cut into more cells than the budget allows (--budget divided by 50), or the rule's answer does not cut into finitely many columns. A claim that was not proven is never green, so this is an error and not a warning.
Fix. Raise --budget (both rulec check and ritsu check take it), or give the table that decides the transitions fewer columns. The cost is the product of the columns, so a chain of tables is cheaper than one wide one.
Smallest reproduction (with --budget 100):
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
machine k(k) over m
carry st -> nx
initial a
final c
E129
error — This rulec reads no koyomi file
When. A rule that takes a range from a koyomi date (range from koyomi) is checked by a rulec with no koyomi joined: the binary of rulec's own crate. The check does not fall back to every day of the range; the file counts as not checked, and the run exits 2.
Fix. Run ritsu rulec check <file> or ritsu check <dir>; both hand the set of days over through koyomi's port.
Smallest reproduction:
rule t(t) v1
inputs
d(d) : date range from koyomi "terms.cal" date payment
outputs
r(r) : bool
table j(j)
policy unique
| d | -> r(r) : bool |
| - | true |
With terms.cal beside it:
dates terms v1
inputs
received : date range >=2026-01-01 <=2026-12-20
date closing = received
close day 20
date payment = closing
day 10 of month +1
E130
error — The days of the koyomi date cannot be read
When. koyomi does not answer for the days of the date range from koyomi names: the file is not there, does not pass koyomi's check, or has no date of that name; the inputs come to more combinations than koyomi checks; the computation stops at some input; or the date comes to no day at all. What koyomi says is in the notes. The check does not fall back to every day of the range.
Fix. Make the koyomi file pass ritsu koyomi check, and name a date it has.
Smallest reproduction:
rule t(t) v1
inputs
d(d) : date range from koyomi "terms.cal" date due
outputs
r(r) : bool
table j(j)
policy unique
| d | -> r(r) : bool |
| - | true |
With terms.cal beside it:
dates terms v1
inputs
received : date range >=2026-01-01 <=2026-12-20
date closing = received
close day 20
date payment = closing
day 10 of month +1
E132
error — The divisor can be zero
When. A rounding function's argument divides by a value (half_up(total / units, 1JPY)), and the range of the divisor includes zero. The range is worked out by interval arithmetic from the inputs' ranges, the ranges of the derives and the defines, and what the tables above write — where a row writes one of its own table's columns, only the values its cell there lets through (| >=1 | units | writes units of one or more). A constraint is not read. A divisor that reads a value with no range cannot be shown never to be zero either, and is this too. Where an input that makes the divisor zero is found, the notes name it.
Fix. Narrow the divisor's range so that it leaves zero out (range >=1), divide by max(units, 1), or put a table that picks another value when it is zero (the two rows | 0 | 1 | and | >=1 | units |) and divide by that table's output. Dividing by b - a under constraint a < b, write max(b - a, 1) too: under the constraint it is the same value. What to answer when it is zero is a business decision, and rulec does not make it.
Smallest reproduction:
rule average_price v1
inputs
total : money[JPY] range >=0JPY <=10000JPY
units : number range >=0 <=100
outputs
average : money[JPY] round half_up(1JPY)
define per_unit : money[JPY] = half_up(total / units, 1JPY)
result average = per_unit
Related codes: E115, E108, E019
E139
error — The argument of isqrt can be below zero
When. The range of x in isqrt(x) includes values below zero. The range is worked out by intervals as for E132, and an argument that reads a value with no range is this too. A number below zero has no square root, and the generated code would have no answer on that call. Where an input that makes the argument negative is found, the notes name it.
Fix. Narrow the inputs' ranges so that the argument stays at zero or more, or pass isqrt(max(x, 0)). A power is written as a product: isqrt(n * n * n) is n to the power 1.5, rounded down.
Smallest reproduction:
rule root_of_gap v1
inputs
gap : number range >=-10 <=100
outputs
root : number round down(1)
define whole_root : number = isqrt(gap)
result root = whole_root
E140
error — An output's range does not hold the values it takes
When. The range written on an output's line leaves out values the output can take. What it can take is what decides it — the cells of a table's rows, a define of its name, the expression of the result — under the inputs' ranges and the constraint lines, put out to the grid of its rounding. Where rulec cannot work that out (the output reads an input with no range, or is no number), the range cannot be checked, and is this too. An output's range is the range other languages (kaname, dandori, …) read the rule's answer in, so one too narrow would let their checks of ranges pass over values the rule really returns.
Fix. Widen the range to the values the message states (fix.text holds it, as range >=0USDc <=1000USDc). Where it cannot be checked, give the inputs the output reads a range, or take the output's range off: an output with none written has the range rulec works out, and the other languages read that.
Smallest reproduction:
rule fee v1
inputs
amount : money[USDc] range >=0USDc <=10_000USDc
outputs
fee : money[USDc] round down(1USDc) range >=0USDc <=500USDc
result fee = amount × 10%
A range that cannot be checked:
rule fee v1
inputs
amount : money[USDc]
outputs
fee : money[USDc] round down(1USDc) range >=0USDc <=500USDc
result fee = amount × 10%
Related codes: E112, E061, E103
E134
error — A call answers a carried number outside its range
When. A call from a state a case can reach from initial answers a carried number outside its input's range: the next call cannot be passed it, and the case cannot go on from there. The shortest sequence of calls comes with it as witness.trace. A row that leaves the range only from states no case reaches is W126's.
Fix. Correct the row that decides the next value (stop at the bound, or move on to another state), or widen the input's range if the value does occur in the business.
Smallest reproduction:
rule t(t) v1
enum s(s) = run(run) | done(done)
inputs
st(st) : s
again(again) : bool
n(n) : number range >=0 <=3
outputs
nx(nx) : s
nn(nn) : number round down(1)
define more(more) : number = n + 1
table m(m)
policy first
| st | again | n | -> nx(nx) : s | nn(nn) : number |
| run | true | <=2 | run | more |
| run | true | - | done | more |
| run | - | - | done | n |
| done | - | - | done | n |
machine k(k) over m
carry st -> nx
carry n -> nn
initial run
initial n 0
Related codes: E072, E135, W126
E135
error — An always line is broken
When. Some sequence of calls reaches a state the always line speaks of (those after when, or any state when there are none) with a carried value its cell does not accept. The shortest one comes with it, and every call in it is an input the rule takes and answers. A place where a carried number has left its range is E134's, and is not looked at here.
Fix. Correct the rows that decide where a case goes and what it carries on, or the always line if the claim is wrong as business.
Smallest reproduction:
rule t(t) v1
enum s(s) = run(run) | done(done)
inputs
st(st) : s
n(n) : number range >=0 <=3
outputs
nx(nx) : s
nn(nn) : number round down(1)
define more(more) : number = n + 1
table m(m)
policy first
| st | n | -> nx(nx) : s | nn(nn) : number |
| run | <3 | run | more |
| - | - | done | n |
machine k(k) over m
carry st -> nx
carry n -> nn
initial run
initial n 0
always n <=2 when run
Related codes: E072, E126, E134
E136
error — The prerequisites go round in a cycle
When. Following what a value needs, and what that needs, leads back to the value. The values inside an or are followed too. One is given for each set of values that reach one another, with one way round from the first value of the set back to it, and the values of the set. Each value on a cycle waits for another on it, so the cycle cannot be entered anywhere; where another value of an or does lead in, a cycle is still where prerequisites are misread, and it is an error.
Fix. Drop one of the prerequisites on the cycle.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| - | true |
prerequisites p(p) over k
start a
b requires c
c requires b
E137
error — A value cannot be reached from start
When. Starting from the values of start and adding every value each group of whose needs has a value already held, some value is never added. With no cycle, what is never added waits on a value no case can take (requires nothing): one is given for each value with a group of such values alone, and the values that wait on it are listed in a note. A cycle is E136's and is not looked at here. A value written as one no case can take is not said: it says so itself.
Fix. Drop the prerequisite no case can take, or, if the value cannot be had either, write <value> requires nothing "<reason>".
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| - | true |
prerequisites p(p) over k
start a
b requires c
c requires nothing "retired"
E138
error — A prerequisite comes after the value in the order of ordered by
When. In the table ordered by names, a value's answer comes before the answer of something it needs (a value inside an or too). A prerequisite no case can take, and one on a cycle, are E137's and E136's, and are not looked at here.
Fix. Correct the table's answer, or the prerequisite.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
enum s(s) = one(one) < two(two)
inputs
x(x) : k
outputs
r(r) : s
table j(j)
policy unique
| x | -> r(r) : s |
| a, c | one |
| b | two |
prerequisites p(p) over k
start a
b requires a
c requires b
ordered by j
W125
warning — No sequence of calls reaches a state
When. The enum of the carried state has a value that no sequence of calls from initial reaches.
Fix. Add the transition into it, or drop it from the enum. If it is kept for cases moved over from another version, leave it: diff looks at the move.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c) | d(d)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
| d | - | d |
machine k(k) over m
carry st -> nx
initial a
final c
W126
warning — A transition is never taken from a state a case can reach
When. A row of the table that decides the transitions applies only in states no sequence of calls from initial reaches. It comes with W125. A row whose state a case does reach, but never with the value of a held input or of a carried value (a carry line after the first) the row needs, is this warning too — among them a row that would answer a carried number outside its range, from values no case holds.
Fix. Add the transition into the state it needs, or drop the row.
Smallest reproduction:
rule t(t) v1
enum s(s) = a(a) | b(b) | c(c) | d(d)
enum e(e) = fwd(fwd) | rev(rev)
inputs
st(st) : s
ev(ev) : e
outputs
nx(nx) : s
table m(m)
policy unique
| st | ev | -> nx(nx) : s |
| a | fwd | b |
| a | rev | a |
| b | fwd | c |
| b | rev | a |
| c | - | c |
| d | - | d |
machine k(k) over m
carry st -> nx
initial a
final c
W127
warning — A claim of the machine could not be settled
When. A claim turns on a cell for which no input was built and none was shown impossible. Derived values that share an input, with a solution only among the rationals, are what is left here — the place W114 is about. A machine that carries numbers (a carry line after the first) leaves three more: the numbers were walked in intervals, since every value one by one is past the budget, and a sequence that breaks the claim over the intervals could not be made again from calls that really happen; walked in intervals, that every state a case reaches can still get to a final one is not followed; or a carried number's next value moves with another input inside a cell, and the values between the ones the calls seen answered were not each tried. What was not settled is not said to hold.
Fix. If no input really falls in the cell, write the condition over whole numbers (a >= 3JPY for twice >= 5JPY). If one does, write it down as an example or a scenario. Where the carried numbers were walked in intervals, raising --budget or narrowing their ranges lets every value be walked one by one, and the claim settled.
Smallest reproduction:
rule t(t) v1
enum s(s) = p(p) | q(q)
inputs
st(st) : s
a(a) : money[JPY] range >=0JPY <=100_000JPY
outputs
nx(nx) : s
derive twice(d) : money[JPY] = a + a range >=0JPY <=200_000JPY
define above(up) : bool = twice >= 5JPY
define below(dn) : bool = twice <= 5JPY
table m(m)
policy first
| st | above | below | -> nx(nx) : s |
| p | true | true | q |
| - | - | - | st |
machine k(k) over m
carry st -> nx
initial p
W128
warning — No input was found that reaches a row, and none was shown not to
When. A row that writes a cell on a computed column (a derive, a define): no input reaching it was found among the rule's examples, by the search the vectors are made with, among the combinations of the inputs' candidate values (up to 4,096), or as the input behind the gap the completeness check finds with the row taken out, and it could not be shown that no input does. The point of such a row is not an input, and whether one reaches it turns on how the expressions relate (a value only whole numbers reach, or one they never do). A row nothing decided is not passed in silence. A row of inputs and enums alone needs no search: its point is an input.
Fix. Look at the expressions and the row's conditions together. If some input reaches the row, write it as an example and the warning goes; if none does, delete the row. If deleting it brings a completeness gap (E101), rulec cannot show that the combination never comes either, and the row can stay.
Smallest reproduction:
rule square_band v1
inputs
side : number range >=0 <=10
outputs
band : number round down(1)
define area : number = side * side
table band_of
policy first
| area | -> band : number |
| 2 | 1 |
| - | 2 |
Related codes: E101, E102, W114, W127
W130
warning — A prerequisite is already needed through another
When. One of a value's prerequisites is among what another of its prerequisites needs, or what that needs in turn: written or not, it leaves the value to be taken at the same times. The values inside an or are left out on both sides.
Fix. Drop it: one place less to correct when the prerequisites change.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b) | c(c)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| - | true |
prerequisites p(p) over k
start a
b requires a
c requires b, a
Related codes: E136
W105
warning — Shadowing that needs review: an earlier row hides part of a later one
When. In a policy first table, two rows partially intersect and disagree on the output. Structural shadowing (the staircase) and equivalent shadowing are folded into a count line; only the pairs that need review are listed. A pair that meets only where no input reaches is not reported, as under E105.
Fix. If it is intended, leave it: check --diff-base in CI reports only newly created pairs. To let the later row win, move it above the earlier one. --show-shadow lists every pair.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b)
inputs
x(x) : k
y(y) : bool
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy first
| x | y | -> r(r) : money[JPY, incl_tax] |
| a | - | 100JPY |
| - | true | 200JPY |
| - | - | 300JPY |
Related codes: E105, W110, E102
W110
warning — A first table with no overlaps
When. The table is policy first, yet no two rows overlap. The order carries no meaning, and unique is the stronger guarantee.
Fix. Change it to policy unique. From then on, the check guarantees that reordering the rows cannot change the meaning.
Smallest reproduction:
rule t(t) v1
enum k(k) = a(a) | b(b)
inputs
x(x) : k
outputs
r(r) : bool
table j(j)
policy first
| x | -> r(r) : bool |
| a | true |
| b | false |
W111
warning — A declaration is never used
When. An input, a derived value, a group or an enum value appears in no cell of any table. It can be the symptom of a forgotten column, or a legitimate contract. Values of an imported type are not checked this way, nor is a value of an ordered enum when every name of that type is an input whose range leaves the value out: no call brings it. Nor is a value a cell that hands an input on (| false | requested |) answers with, when no table tests the enum in a column: no table could give it a row of its own.
Fix. If the input serves only as a range check at the entry, mark the declaration contract_only. If an enum value is meant to fall through to the default row, mark the value default. Otherwise either add the column to a table, or delete the declaration.
Smallest reproduction:
rule t(t) v1
inputs
x(x) : bool
w(w) : mass[g] range >=0g <=10kg
outputs
r(r) : bool
table j(j)
policy unique
| x | -> r(r) : bool |
| true | true |
| false | false |
W116
warning — No example uses this sequence
When. A sequence is written and no example names it. A sequence runs only when an example names it, so this one never runs.
Fix. Add the example that walks it, or drop the sequence. Written and unused is usually the trace of an example left unwritten.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| <=5JPY | a |
| >5JPY | b |
fold d over xs
a -> next
b -> take_first k
empty -> 0JPY
exhausted -> held
sequence s(s)
| k |
| 3JPY |
W119
warning — A pinned fragment is not cited
When. A pin line sits under a source line, but no @ in the rule cites that fragment. It is what remains after a citation was removed.
Fix. Remove the pin line; rulec source pin does.
Smallest reproduction:
rule t(t) v1
source 法 = law "000AC0000000001" asof 2026-04-01
第1条 sha256:ce31217424a10206
第2条 sha256:0000000000000000
inputs
a(a) : bool
outputs
x(x) : bool
table 表(t1) @法 第1条
| a | -> x |
| - | true |
With sources/law/000AC0000000001@2026-04-01/MainProvision-Article_1.xml beside it:
<Article Num="1"><ArticleTitle>第一条</ArticleTitle><Paragraph Num="1"><ParagraphNum/><ParagraphSentence><Sentence>甲は、乙とする。</Sentence></ParagraphSentence></Paragraph></Article>
Related codes: E037
W120
warning — The copy states a value no row uses
When. A cell of the copy a table cites whole with @source table1 is nothing but a number, and no row uses that value. A dropped row does not show up in the completeness check: its inputs fall into one of the rows that remain. Only cells that are nothing but a number are asked about, so revised 1 April 2026 is not counted as an amount, and a row that merges what the copy lists — <=3kg over its 1kg, 2kg and 3kg — accounts for all of them.
Fix. Compare the table with the copy and check that no row was left out; if a revision added a row, transcribe it. If the table transcribes only part of the fragment — one origin of a tariff sheet that lists several — move the citation from the table line onto the end of each row that came from it. A row's citation says only where that row came from, so the rest goes unasked while the amounts are still held to the copy (E116).
Smallest reproduction:
rule t(t) v1
source tariff = file "tariff.md" sha256:583beb1ff0102e2e
table1 sha256:07423c36f9e9259f
inputs
a(a) : bool
outputs
x(x) : money[JPY] round down(1JPY)
table t1(t1) @tariff table1
policy unique
| a | -> x |
| - | 990JPY |
With tariff.md beside it:
With tariff.md.fragments/table1.tsv beside it:
W118
warning — No row of an applied table is reached in this apply
When. Every row of an applied table or clause is unreachable in this rule: what is bound never reaches its conditions, or other definitions of this rule (a clause with overrides apply:table, say) take precedence over all of it. Rows unreachable one by one draw no word: a callee's table is usually written for a wider range than this rule's, and doc lists those rows as unused by this apply.
Fix. If this apply does not need the table, leave it out with except <table>. If it should be used, look at the bindings and the with mapping.
Smallest reproduction:
rule t(t) v1
inputs
n(n) : number range >=1 <=10
outputs
y(y) : number round down(1)
apply call(c) = "callee.rule" sha256:1c554496c37f709f
a = n
x -> y
clause special(special) -> call:x
when always
then 3
overrides call:t
With callee.rule beside it:
rule callee(callee) v1
inputs
a(a) : number range >=1 <=10
outputs
x(x) : number round down(1)
table t(t)
policy unique
| a | -> x |
small | <=5 | 1 |
large | >5 | 2 |
W117
warning — An exception with no effect
When. No row of this table meets a row of what its overrides line names. Precedence decides which wins when an input matches both, so with no meeting rows the line decides nothing. It is the usual sign of a proviso transcribed so that it no longer carves out part of the main rule.
Fix. Compare with the source and fix the conditions of this table's rows or of the target's. If they really never meet, drop the overrides line.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : bool
outputs
x(x) : bool
table base(base)
| a | -> x |
r1 | true | true |
r2 | false | false |
table special(special)
overrides base:r1, base:r2
| a | -> x |
| true | false |
W121
warning — An identifier collides, or cannot be used, in a target language
When. An ASCII alias is a keyword of one of the targets, or a name that language already uses. An alias becomes a function, a parameter, a type or a member there. The rule's alias also names a module or a package, and the standard library's own names are held against it there. Or a rule and an enum it declares take one type name: Go declares the rule's function and the enum's type in one package, and Java nests the enum in the class the rule is, so neither compiles (an enum read from an enums file is written by that file's package or class, and does not clash). It is raised too for a name with no alias that is a keyword: the generated code of every language then writes the name with _ after it (class_), and with a number after that where class_ is the identifier of another name of the rule (class_2). It is raised as well where two names the author wrote, whose identifiers (the alias, else the name) differ, are one identifier in the spelling of some target: the lowerCamelCase values are written in, the PascalCase of a struct's fields, the JSON name of a field of the .proto, the upper case of an enum's values, the _ put after a keyword or after the name of a variable a language keeps, a column's name read with case set aside (SQLite), and the first 63 bytes of a name (PostgreSQL). And where a target cannot take an identifier: a language that reads a name starting with a capital as a constant, a NamedTuple, which takes no field starting with _, PostgreSQL, which cuts a column's name longer than 63 bytes, the languages in which a name of _ alone is neither a variable nor a field, SQLite, which gives a column no name of true or false, and the .proto, which takes no name that is not in ASCII. And where a name the author wrote is one the generated code uses itself: the types of the rows that matched, of the answer and of one element, the errors, the helpers that round and the rest, its locals, the names the language has built in, the messages and the fields of the .proto (the trace of DecideResponse), the <TYPE>_UNSPECIFIED of an enum's values, the names the service of Connect imports, the package names a language's command keeps, and the name of the file a program starts in. The warning's notes say which targets. Two names every language spells as one (the types of two enums, say) stop the check with E034.
Fix. A keyword means the generated code for that language does not compile (type as an input's alias breaks Rust). A name that is taken means the rule's function or an enum's type hides it (sum as the rule's alias hides Python's builtin). A module's name means, with time as the rule's alias, that the module generated as time collides with the standard library's time; the warning's notes say where and how. A parameter or a local shadows nothing outside its own body, so there the warning is raised only for a keyword. Of two names that are one identifier in a target you generate, give one an alias; to an identifier a target cannot take, or a name its generated code uses itself, give an alias it takes (one that starts with neither a capital nor _, is 63 bytes or fewer, and is no name the generated code uses). For a target you do not generate, leave it.
Smallest reproduction:
rule t(t) v1
inputs
kind(type) : number range >=0 <=10
outputs
o(o) : number round down(1)
table j(j)
policy first
| kind | -> o(o) : number |
| >=5 | 1 |
| - | 0 |
Two names Swift and Java spell alike:
rule flat_fee v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
define base_fee : money[USD] = 3USD
define baseFee : money[USD] = 2USD
table fee_by_qty
policy unique
| qty | -> fee |
| <=5 | base_fee |
| >5 | baseFee |
Two names SQLite reads as one column:
rule small_fee v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
define afee : money[USD] = 3USD
define aFee : money[USD] = 2USD
table fee_by_qty
policy unique
| qty | -> fee |
| <=5 | afee |
| >5 | aFee |
Two long names PostgreSQL cuts to one:
rule spring_fee v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
define the_fee_for_a_small_order_placed_before_the_spring_campaign_of_2026_began : money[USD] = 3USD
define the_fee_for_a_small_order_placed_before_the_spring_campaign_of_2026_ended : money[USD] = 2USD
table fee_by_qty
policy unique
| qty | -> fee |
| <=5 | the_fee_for_a_small_order_placed_before_the_spring_campaign_of_2026_began |
| >5 | the_fee_for_a_small_order_placed_before_the_spring_campaign_of_2026_ended |
A name Ruby reads as a constant:
rule base_fee v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
define Base : money[USD] = 3USD
table fee_by_qty
policy unique
| qty | -> fee |
| <=5 | Base |
| >5 | 2USD |
A name the generated code uses itself:
rule fee_and_trace v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
trace : number round down(1)
table fee_by_qty
policy unique
| qty | -> fee | trace |
| <=5 | 3USD | 1 |
| >5 | 2USD | 2 |
A name of _ alone:
rule underscore_fee v1
inputs
qty : number range >=0 <=10
outputs
fee : money[USD] round down(1USD)
define _ : money[USD] = 3USD
table fee_by_qty
policy unique
| qty | -> fee |
| <=5 | _ |
| >5 | 2USD |
Related codes: E009, E011, E034
W115
warning — No element can land on this verdict
When. A fold has an arm for a verdict no row produces. It is the other side of E024: not a hole but an arm nothing reaches, and more often the trace of a table that changed than of an arm written by mistake.
Fix. Look again at the table's rows, or drop the arm. Which of the two is right is decided by reading the table, not this message.
Smallest reproduction:
rule t(t) v1
enum v(v) = a(a) | b(b)
elements xs(xs)
k(k) : money[JPY, incl_tax] range >=0JPY <=10JPY
outputs
r(r) : money[JPY, incl_tax] round down(1JPY)
table j(j)
policy unique
| k | -> d(d) : v |
| - | a |
fold d over xs
a -> take_first k
b -> next
empty -> 0JPY
exhausted -> held
Related codes: E024
W114
warning — Unconfirmed overlap: an input may match both rows
When. Two rows of a policy unique table may overlap, but no input producing that was constructed and infeasibility was not proven either. Derived values sharing an input and the thresholds inside a boolean define no longer fall here: the elimination decides them. What is left is what it cannot decide because it works over the rationals — twice above is always even and never exactly 5JPY, but 2.5JPY is a rational. A system past the cap of 400 inequalities and one that spans two units are the same: not proven, which is not the same as possible. A pair that meets only where the rows of a table above leave a derive no values does not fall here either: the check reads that and drops it.
Fix. If an order satisfying both conditions can exist, fix the rows: the outputs differ, so a match is a contradiction. If none can exist, leave it — the generated code carries a guard that returns an error rather than silently picking the earlier row.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : money[JPY] range >=0JPY <=100_000JPY
outputs
r(r) : bool
derive twice(d) : money[JPY] = a + a range >=0JPY <=200_000JPY
define above(up) : bool = twice >= 5JPY
define below(dn) : bool = twice <= 5JPY
table j(j)
policy unique
| above | below | -> r(r) : bool |
| true | - | true |
| - | true | false |
| false | false | false |
Related codes: E105, W105, E109
W901
warning — A key is written in the file
When. Somewhere in the rule, in a string or a comment alike, there is a value in the shape of a key: an AWS access key ID, a key or token of GitHub, Slack, Stripe, OpenAI, Anthropic or Google, a Slack incoming webhook URL, or a PEM private key, each a shape its provider fixes. Every language of ritsu looks for them the same way. The diagnostic gives the kind of key, its prefix and its length, and never the key nor its line.
Fix. Keep the key where the code runs (an environment variable, the platform's connection or secret store) and read it from there. If it is real, revoke it with its provider first: taking it out of the file leaves it in the history of the repository. If it is a value for tests, write ritsu: test secret in a comment on the same line.
Smallest reproduction:
rule seats v1
# the Google Sheets API key the counts were read with: AIzaSyD-ritsu-fake-key-for-tests-000000
enum kind = workshop | talk
inputs
event_kind : kind
outputs
needed : number round down(1)
table pick
policy unique
| event_kind | -> needed : number |
| workshop | 30 |
| talk | 80 |
E074
error — A from sum, from max or from min, or an elements … from, is misshapen
When. A from sum, from max or from min has no of <field of an element>; a from max or from min has no empty <value>, or its empty value is not a value of the input's type, or lies outside the input's range; or of or empty is written on a from that takes none (only max and min need empty: a sum of none is 0, and a count of none is 0). Also: something other than from <shape>.<collection> follows the name of an elements line; a field of a sequence taken from a contract has no from; a field of a sequence not taken from one has one; or the from of a field of an element is not a path inside the element.
Fix. Correct the shape: from max order.lines of amount empty 0JPY. empty is the input's value when no element is looked at, and what it is is a business decision. To take a sequence from the contract, write elements lines from order.lines, and on each field the path it is read from in an element, such as from qty. If the caller builds the sequence and passes it, take the from off its fields.
Smallest reproduction:
rule t(t) v1
shape order(order) = jsonschema "order.json" "#/$defs/Order"
inputs
top(top) : money[JPY] range >=0JPY <=1000JPY from max order.lines of amount
outputs
x(x) : bool
table t1(t1)
policy unique
| top | -> x |
| - | true |
With order.json beside it:
{"$defs":{"Order":{"type":"object","required":["lines"],"properties":{"lines":{"type":"array","maxItems":10,"items":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":0,"maximum":1000}}}}}}}}
Related codes: E120, E121, E122, E022
E070
error — A random input or a claim of chances is not shaped right
When. An input marked random cannot be drawn: a number without both ends of its range, an input that may be absent (?), a date or a string, an input read out of the caller's object with from (what from sum and the like read is the caller's value, not a draw, whether random is written before the from or after it), or an input a machine carries. Or random is written on an output or on a field of an element. Or chances is misshapen: something follows chances on its line, there is no table under it, the header has no ->, its last column is not chance, it names no output, or there are no rows; an input that is not random has no column, a random input or a name that is not an input is on the left, a name that is not an output is on the right, a sequence's column in a rule that walks holds something other than the name of a sequence, or a chance cell cannot be read as a rational, a rate or comparisons with one, between 0 and 1. A chances table in a rule with no random input is this too.
Fix. Give a number that is drawn a range with both ends, as in range >=0 <=13, and draw a number, an enum or a truth value. The header of chances names every input that is not random, then ->, the outputs the claims are about, and chance last. A column that takes any value gets -, and a chance is written like 5/14, 35% or <=1/100.
Smallest reproduction:
rule t(t) v1
inputs
roll(roll) : number random
outputs
r(r) : bool
table j(j)
policy unique
| roll | -> r(r) : bool |
| <5 | true |
| >=5 | false |
Related codes: E073, E133, E108
E073
error — A pick by weight is not shaped right
When. A take_weighted has no by; there are two draw lines; or a fold with no take_weighted has a draw line. Or a rule has two folds that pick by weight; such a fold has an arm other than next and take_weighted; a weight is not a number, or can be negative; there is no draw line; or what draw names is not an input marked random that is a number whose range starts at 0.
Fix. Write the arm take_weighted <value> by <weight> and one line draw -> <random input>, and make the input it names a random number with range >=0 <=<top>. A weight is a whole number, 0 or more. Split the rule in two if it needs two picks.
Smallest reproduction:
rule t(t) v1
enum v(v) = skip(skip) | pick(pick)
elements os(os)
ix(ix) : number range >=1 <=9
ok(ok) : bool
w(w) : number range >=0 <=10
inputs
roll(roll) : number range >=0 <=99 random
outputs
p(p) : number round down(1)
table j(j)
policy unique
| ok | -> v(v) : v |
| true | pick |
| false | skip |
fold v over os
skip -> next
pick -> take_weighted ix by w
empty -> 0
exhausted -> held
E133
error — A claim of chances does not hold
When. The chance a row of chances states is not the chance, with the random inputs drawn uniformly, at some combination of the inputs that are not random its cells let in (or does not pass the comparisons it states). The combination comes with it, and the chance there: an exact rational, from counting the points of the random inputs.
Fix. If the table is right, correct the claim; if the claim is the business truth, correct the table's rows.
Smallest reproduction:
rule t(t) v1
inputs
roll(roll) : number range >=0 <=9 random
outputs
r(r) : bool
table j(j)
policy unique
| roll | -> r(r) : bool |
| <3 | true |
| >=3 | false |
chances
| -> r | chance |
| true | 1/3 |
Related codes: E070, W129, E107
W129
warning — A claim of chances could not be decided within the budget
When. Where an answer moves inside a box with the random inputs, or the random inputs are read together with the others, the points that have to be run one by one go past the budget of one claim (one 50th of --budget's nodes, a million points by default). A sequence that cannot be read, or a point where the rule contradicts itself, leaves the claim undecided too. A claim that is not decided is said to be neither holding nor broken.
Fix. Narrow the cells of the inputs that are not random, or raise --budget.
Smallest reproduction:
rule t(t) v1
inputs
roll(roll) : number range >=0 <=999 random
a(a) : number range >=0 <=9999
outputs
r(r) : bool
derive d(d) : number = roll - a range >=-9999 <=999
table j(j)
policy unique
| d | -> r(r) : bool |
| <0 | true |
| >=0 | false |
chances
| a | -> r | chance |
| - | true | >=0 |
W131
warning — The chances function runs the rule many times on every call
When. The points of the random inputs, multiplied together, are more than a million, and the function generated to count the answers runs the rule that many times on every call. A rule that picks by weight, its random input read by the draw line alone, is counted from its weights and does not get it.
Fix. If it is called often, narrow the ranges of the random inputs; in a rule that picks by weight, let only the draw line read the random input.
Smallest reproduction:
rule t(t) v1
inputs
a(a) : number range >=0 <=999 random
b(b) : number range >=0 <=9999 random
outputs
r(r) : bool
table j(j)
policy unique
| a | b | -> r(r) : bool |
| <500 | - | true |
| >=500 | - | false |
E067
error — A contribution is not shaped as one
When. A file headed contribution cannot be read as one. Its to line is missing or written twice, or the file it names cannot be read or does not pass check. An extend adds a value to an enum not marked open, to an enum another enums file owns, a value the enum has, or a value with no after to an ordered enum. An into names something that is not an open value, or a term that is declared nowhere or is decided below the open value. A table or clause decides an output no definition opens, or one of an open output reads a name below the output's last definition. Lines are added to a prerequisites block not marked open, or a line of one of the block's own values. The after lines go round, two contributions share a name, the file holds a line a contribution cannot (outputs, result, apply, a machine, a sequence it walks, enums, imports, sources, contracts, constraints, claims of chances), or an input is drawn at random.
Fix. A contribution adds only to the places the file it adds to opens with open. Where it does not, ask its author to open it, or read it with apply and write a rule of your own. The notes say where it can add.
Smallest reproduction:
With base.rule beside it:
rule base(base) v1
enum kind(kind) = a(a) | b(b)
inputs
k(k) : kind
outputs
fee(fee) : number round down(1)
table t(t)
policy unique
| k | -> fee : number |
| a | 1 |
| b | 2 |
Related codes: E068, E040, E033, E131
E068
error — An open value is not shaped as one
When. An open line has no way of combining (combine sum, product, max or min), no range, or no empty for max and min; its type is not one the way of combining puts together (a sum takes numbers, money, quantities and rates, a product numbers and rates, max and min those and temperatures and sound levels); it is optional; or a term is on a finer step than the value. The range is the promise that the value stays inside it whatever is added, and the tables below are checked over the whole of it.
Fix. Write it open <name> : <type> combine sum|product|max|min [empty <value>] range >=… <=…. empty is the answer when there is no term, and it takes part as the smallest answer for max (the largest for min). Round a finer term to the value's step before adding it.
Smallest reproduction:
rule t(t) v1
inputs
member(member) : bool
outputs
bonus(bonus) : rate[step 1%] round down(1%)
table p(p)
policy unique
| member | -> from_member : rate[step 1%] |
| true | 10% |
| false | 0% |
open bonus : rate[step 1%] combine sum
from_member
E131
error — A value put together leaves the range it declares
When. The interval of an open value's terms — the rule's and those contributions add — put together as its combine says, leaves the range on the open line. The notes give each term's interval, the contribution each comes from, and an input that takes the value out of the range. The range is the promise that the value stays inside it whatever is added, and the tables below are checked over the whole of it; a value outside it would get an answer nothing checked.
Fix. Widen the range (the tables below are then checked over the wider range), or narrow what the contribution adds. Which is a business decision, so there is no fix.text.
Smallest reproduction:
contribution c(c) v1
to "base.rule" signature:b10f6ca4271b3f2f
inputs
promo(promo) : bool
table q(q)
policy unique
| promo | -> from_promo : rate[step 1%] |
| true | 15% |
| false | 0% |
into bonus
from_promo
With base.rule beside it:
rule base(base) v1
inputs
member(member) : bool
outputs
bonus(bonus) : rate[step 1%] round down(1%)
table p(p)
policy unique
| member | -> from_member : rate[step 1%] |
| true | 10% |
| false | 0% |
open bonus : rate[step 1%] combine sum range >=0% <=20%
from_member
Related codes: E068, E112, E067
W132
warning — A term of an open value changes nothing
When. A term of an open value cannot move the value whatever it takes: in a max, every value of the term is at or below the empty value; in a min, at or above it; in a sum, it is always 0; in a product, always 1. The empty value of max and min is the value the terms start from, and it always takes part, not only when there is no term, so a cap written as a max leaves its terms with nothing to do, in silence. It is said of a term a contribution adds and of a term of the rule alike.
Fix. If a term of a max sets a cap, put the value together with min (and with max if a term of a min sets a floor). If the term can pass the empty value — above it for a max, below it for a min — write the values that do. If it is not needed, take it out.
Smallest reproduction: