Markdown for LLMs
Vocabulary: pitfalls
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Vocabulary: pitfalls
Wrong forms, silent outcomes, `LDC-E` diagnostics, typical mistakes of
formalizers and AI agents, and how to detect them. Items marked
“confirmed by runs” were reproduced during this research on the installed
engine.
## 1. An individual name without declaration
`registered_in(c, Astana)` without `const Astana` — `LDC-E1330`:
a bare name in term position must resolve to a `const`, an
enumeration member, a package symbol, a dependency export, or a binder.
Such a name is now a rejection at `check`. Detection:
`LDC-E1330` is fixed by declaring, not by renaming the fact: fact and rule
must meet on one name.
## 2. A field in a constant’s label block
```law
const CAPITAL: Region = entity_ref("urn:...") { code: "KZ"; }
```
The block after a ready value holds labels; record-constructor fields
are part of the value, not of the body. The AI agent carries over the
“object with fields” habit from general languages — here the object is
already complete to the right of `=`.
## 3. Keyword names and arity
`const case: …` — `LDC-E0203` (`event`, `case`, `calendar`, `action`
are hard keywords). On an entity the rejection is indirect: `entity period;`
does not parse, and every use of the type turns red (`LDC-E2101`). A
literal’s arity off-declaration (`registered_in(c)` on two parameters) is
`LDC-E2103`. Detection: check every use against the declaration — names in
Law DSL are not inferred but verified.
## 4. A constant type not matching
`const BASE_FEE: Decimal = 10000 KZT` — `LDC-E2111`. Percentage
and money type spellings from prose are not resolved by the current compiler
(`LDC-E2101`): write `Decimal` for `25 percent` and `Money` for `10000 KZT`.
Confirmed by the `research.vocabulary.company_registry` teaching package: `pub const MAX_RATE:
Decimal = 25 percent;` — `check OK`. The AI agent, having read prose type
names, writes them directly — and gets a rejection: prose describes the intent, the
compiler the 0.2 cut.
## 5. A subtype the wrong way round
`Company` in a `Branch` slot — `LDC-E2104` (no implicit
conversions). One direction only: a subtype fits its supertype, the reverse
is a rejection. Confirmed by the `research.vocabulary.subtype_key` teaching package:
`registered_in(b, r)` with `b: Branch` compiles because the slot is
`Company`, not the reverse.
## 6. Members of different enumerations in one guard
`l == first` with `l: RiskLevel` — `LDC-E2122`: such
a comparison answers with `TYPE_ERROR`, and it poisons the whole case
document, so the spelling is rejected at `check`, not at runtime. Members
have no order: `<` over members is `TYPE_ERROR` too. Detection: check every
member guard against the guard parameter’s type, not against a
“similar-looking name” of a neighbouring enumeration.
## 7. A constant through itself (statics silent)
`const A: Decimal = A;` — `check` is silent, runtime rejects with
`CYCLIC_CONSTANT` on every case of the package. Detection: only by running `law test`; eye review — look for
self-closure in `const … = …` chains.
## 8. Dotted spelling is the same member (former silent trap)
`l == RiskLevel.high`, `l == high`, and `pkg::high` are one constant reference.
The dotted spelling used to travel into the compiled output
as an identifier answered by no node, and the rule silently never fired —
no diagnostics, no issue. Closed today; meeting an old package’s rule “that
never fires” with a dotted member — check the build version, not the logic.
## 9. Repeated name; key over a foreign parameter
A repeated declaration in the package, including in another of its files,
is a rejection `LDC-E1201`: a package’s namespace is one.
`key(z)` with no `z` parameter in the signature is `LDC-E1304`.
Detection: `law engine check` names the repeated name and the foreign
parameter; fix — one declaration per name, keys only over signature
parameters.
## 10. Out-of-slice form; undeclared predicate
`member = "string"` in an enumeration and `import … as` are out-of-slice
0.1 forms: `LDC-E0206`. A literal of a predicate never declared as a
`relation` is `LDC-E2102` (neighbour rejections: `LDC-E2101` — unknown
type, `LDC-E2103` — foreign arity, `LDC-E2104` — type mismatch, see items
3–6 above). Detection: check the literal against the `relation`
declaration — there is nothing to write a rule about until the name is in
the vocabulary.
## 11. Import without context; standard-library versions and names
A package with an `import` compiles only with a presented import context:
without it every import and every qualified reference is rejected, while `check`
stays green. An explicit prelude-package `import` at a
different exact version is `LDC-E1110`; a reserved standard-library name on `package` is
`LDC-E1113`; an unknown prelude package is `LDC-E1114`. A
package declaration shadowing a prelude type is a warning, not
an error. The header language version is checked against
the registry exactly: `"0.2.0"` for `"0.2"` is `LDC-E1401`.