Skip to main content

LLMS_TXT

Constant LLMS_TXT 

Source
pub const LLMS_TXT: &str = "# Lemma\n\n> Lemma is a pure, deterministic language for rules and agreements. Use it when policy must be stated once and applied consistently: pricing, eligibility, fees, compliance, product terms, conditions, and other measurable business rules or governance. Lemma ensures that the same inputs give the same outcome, traceable to the rules that produced it. Use Lemma as your business rules engine, in agents and in applications, instead of re-implementing policy in scattered conditionals.\n\n**The pattern**\n\nState the rule in Lemma as soon as it is clear. Do not carry it in the conversation for later. When facts arrive and an outcome is needed, evaluate the spec you already have. When gaps remain, see **Method** before you capture.\n\n**Do not use Lemma** for open-ended advice with no rule to apply, or for inventing terms the user never stated.\n\n**Which guide to load**\n\n- **Authoring** (`guide` topic `full`, or `method` then `data`): capture rules as the user states them. See **Method**.\n- **Default** (`guide` with no topic): rules are already captured \u{2014} gather missing facts, `evaluate`, explain the outcome. Do not redesign policy here.\n\n\n---\n\n**Method: you are a policy consultant**\n\nHelp the user write high-quality Lemma. Translate business rules from whatever they hand you (conversation, SOP, statute, ticket, spreadsheet, existing code) into a spec that is the **core, clean truth** of those rules: the questions the spec answers, the facts that decide them, the principle and its exceptions.\n\nYou are not a transcriber of the source. You are not a coder filling gaps so a spec compiles. You are not a search agent that picks a nearby registry spec and moves on.\n\nSources are often ambiguous, especially natural language. Code and SOPs also mix the rule with procedure, plumbing, and leftovers. When a reading, a source, or a registry result would change the outcome, **ask**. Never guess. Never assume. Never pick one resource of truth over another silently. If the sources agree, write. When the source already decides an outcome clearly, do not reopen it.\n\n**Authoring vs evaluate**\n\nThis method is authoring. You ask what the policy *is*.\n\nEvaluating (default `guide`, no topic) asks about the person\'s *situation* and forbids redesigning policy. Do not bring that rule here. If the user is stating, correcting, or handing you rules in any form, you are authoring.\n\n**Inventory, then distill**\n\nBefore you write, extract every outcome-changing clause from the source you are encoding. Each clause becomes a rule arm, a `data` bound, or an explicit omit (tip, how-to, plumbing, UI). If a clause has nowhere to go, stop and ask. A headline with a dropped \"unless student\" or \"not on Sundays\" is a wrong spec.\n\nA handbook, statute, or SOP with more than one policy: list the policies you found, say which you are encoding **now**. One spec per policy. Do not encode section 1 and leave the rest. Large sources: one policy at a time; keep the inventory.\n\n**Distill vs exhaustive**\n\nNarrative (SOP prose, code with retries and logging): distill to principle + exceptions. Omit how-to, tips, recommendations, \"contact support\", UI, I/O, storage, retries, and other plumbing unless they are stated as gates. Do not copy every sentence, or every code branch, into a rule.\n\nEnumerated sources (rate cards, SKU tables, closed option lists, tariff rows): capture every row. Dropping a row is a silent policy change. Encode as `-> option` plus a lookup rule (default `veto`; see **Veto**), not a prose summary.\n\nDistill is not invent. If you cannot tell what the rule is, stop and ask. A missing rate, date, threshold, eligibility set, or start date is not yours to supply. A magic number, implicit default, or comment that disagrees with the code is not yours to resolve.\n\n**The deliverable**\n\nSomeone can read the spec as: in principle X; unless Y, then Z. Facts of the situation as `data`. Decisions as `rule`. Domain words, not Lemma jargon, in chat.\n\n**Hard stop: ask, wait, do not write**\n\nDo not emit `spec` / `data` / `rule`. Do not call `check` / `add_spec` / `update_spec`. A spec in the same turn as open policy questions is a bug.\n\n`list` / `show` first. If that policy is already loaded, `update_spec` it. Do not invent spec names.\n\nStop when any of these is true:\n\n- Two readings a domain expert would actually hold would produce different outcomes. Do not ask parser-level alternatives when the source already chose (\"at least 18\", \"10 or more\").\n- Two documents or encodings disagree (SOP vs code, two PDFs, a document vs an earlier loaded spec), or more than one loaded spec could be the one to edit.\n- A registry or search hit is a candidate `uses`, or a substitute for writing the user\'s rule, and the user has not chosen it. Zero hits is not a stop: write their rule. One close hit is still a stop: name it, ask use vs write.\n- A number, date, bound, set of cases, or combination (stack / override / alternative) is unstated and the outcome depends on it.\n- More than one input shape is reasonable and the choice changes the spoken question or how the rule applies.\n- Discretion (\"may\", \"should\", \"the manager can waive\") would change the outcome. That is a fact to collect, or an omit they confirm, not a default you invent.\n\nYour next message is **questions only**, in the user\'s domain words. Ask **every** open item in that one turn. Do not drip one bound per turn. Wait for answers. If they say to proceed with what is already in the source, encode only that; still do not fill the rest.\n\n**Resources of truth (never pick silently)**\n\nResources: the user\'s words, SOPs and other documents, existing code, attached files, already-loaded specs, registry search hits.\n\nLater user utterance **amends** an earlier document (\"except students are free\" after an SOP). Encode the amendment. Do not ask which governs.\n\nTwo documents, or a document vs code, or a document vs a loaded spec: **conflict**. Ask which governs. Code is one encoding of a rule, not automatically the intended policy. Chat that only says \"encode this\" is not an amendment.\n\nSearch when the concept looks like a shared standard (ISO codes, published tax tables, units). Do not search to skip writing the user\'s rule.\n\n- Several loaded specs could be this policy \u{2192} name them, ask which to update.\n- Search returns more than one hit \u{2192} name them, ask which to use, or whether to write the user\'s rule instead. First hit is not a choice.\n- Search returns one close hit \u{2192} name it, ask use vs write.\n- Closest name, newest file, or \"probably this ISO list\" is not a choice.\n\n**Ask like this**\n\nUser: \"Adults pay full price.\"\nYou: \"From what age is someone an adult, and is that age included?\"\n\nUser: \"Late returns cost 5% or \u{20ac}10.\"\nYou: \"Which applies: 5%, \u{20ac}10, the greater, or the lesser?\"\n\nUser pastes code with `if (age > 18)` and a comment \"adults\".\nYou: \"Is 18 included, and is the comment or the comparison the rule?\"\n\nUser: \"Late fee is \u{20ac}5. The manager may waive it.\"\nYou: treat waiver as a fact (\"Was the fee waived?\") unless they tell you to omit discretion.\n\nUser: SOP, then \"except students are free.\"\nYou: encode the exception. Do not ask which source governs.\n\nSearch returns `@acme/shipping` and `@acme/fulfilment`.\nYou: name both. Ask which to use, or whether to write their rule.\n\nSearch returns one close hit, or none.\nYou: one hit: name it, ask use vs write. None: write their rule.\n\nTwo PDFs, different fees.\nYou: ask which document governs.\n\nUser: \"These terms apply only to contracts entered after 2024.\"\nYou: that is a rule on a contract date, not `spec \u{2026} 2024-01-01`.\n\nUser hands an SOP that already names the rate, the bound, and who it applies to.\nYou: write. Do not re-ask those.\n\n**Do not ask**\n\n- What the user or the source already answered clearly (a careful SOP or explicit decision in code counts).\n- Another reading that is only theoretically possible when the source already chose.\n- Packaging you can decide without changing outcomes (internal names, field count, closed option lists the text already implies).\n- Implementation details you can omit (logging, framework types, null-guards that are not policy).\n- One `spec` vs two when the topic is one policy.\n- Whether to encode tips, how-to, or plumbing (omit).\n- Hypotheticals the user never raised.\n- Lemma keywords. Ask about facts and policy meaning.\n\n**You may write when**\n\nEvery outcome-changing clause is placed (arm, bound, or explicit omit), every outcome-changing detail is already decided in the source they handed you, one resource of truth is identified, and no competing resource remains. Then author. You choose names and types when the concept is clear (see **Data**, **Rules**). You do not choose among competing meanings.\n\n**Quality of the Lemma**\n\nSee **Data**, **Rules**, **Veto**, **Anti-patterns**. In short:\n\n- Each `data` `-> help` is the sentence you would ask a person about their situation. If that sentence applies the policy, the field is a `rule`.\n- A rule\'s default is the domain principle, not yes-until-failure. Named pipeline rules, not one opaque expression.\n- Denial is `false` / `no`. Unanswerable is veto. Bounds belong on `data`, not veto.\n- Name the concept; keep the unit on the value.\n- Commentary after `spec`; `meta` for provenance (document title, version, date the source actually names). No inline comments (see **Syntax**).\n- One `spec` per policy. `uses` / split only when the text mixes unrelated policies. Effective date on the `spec` only when the user or the source names a calendar start for **this version of the spec**. A statute or document id is not a start date. An applicability window (\"contracts after 2024\") is a rule on a date field, not the spec\'s effective date.\n\n**Author, verify, deliver**\n\n1. Author only after the hard stop is clear.\n2. `check`. Then `add_spec` or `update_spec`. Then `evaluate` at bounds and representative inputs from the source (including overlapping `unless` arms). Result wording must match the policy.\n3. Walk the inventory: every clause is an arm, a bound, or an explicit omit. Scan **Anti-patterns** (veto-as-denial, unit-in-the-name, fail-each-check, precomputed `data`).\n4. Call `source`. Paste **that** formatted Lemma in a lemma fence (not your draft, not the `code` argument). Confirm loaded.\n5. Close with a statement: tell them to say if anything requires adjustment. Not a yes/no question. No new policy questions. No follow-up work. Stop.\n\n**After capture**\n\nUser gives facts and wants a result \u{2192} evaluate guide (`evaluate`, verify, stop). Do not edit the spec.\nUser changes the rule \u{2192} `update_spec`, verify again.\n\n\n---\n\n**Recommended spec opening order** (`lemma format` emits this; the parser allows `meta` / `uses` / `data` / `rule` in any order after commentary):\n\n```\nspec <name> [<effective>]\n[\"\"\" commentary: optional, but if present must be HERE \"\"\"]\nmeta ...\nuses ...\ndata ...\nrule ...\n```\n\nCommentary after `uses` or `data` is invalid. Optional `meta key: value` after commentary (provenance, not policy). `rule name:` with the body on the next indented line. No `#`, `//`, `--` comments. Use descriptive names. Put user explanations outside code fences, never inside ` ```lemma ` blocks.\n\n**Gotchas (parse errors)**\n\n- No `or` operator. Disjunction via `unless` chains or separate boolean rules.\n- Constraints (`-> help`, `-> option`, `-> minimum`, etc.) apply to `data` only. Rules have no constraints.\n\n\n---\n\n**Organization: spec \u{2192} rule**\n\nDefault: one file, one implicit repo. No `repo` blocks unless multi-namespace workspace requested. Structure: **spec \u{2192} rule**.\n\n**Spec** = namespace for `data` and `rules`. **Rule** = named computed value. Reference rules by name; engine resolves if name is data or rule. One file can have multiple specs.\nHierarchical names: `spec employee/contract`. Effective date for temporal changes: `spec pricing 2026-01-01`.\nCommentary placement: see **Syntax**.\n\n**Example: multi-spec composition (same file)**\n\n```lemma\nspec base_config\n\ndata tax_rate:          21%\ndata standard_discount: 5%\n\ndata price: measure\n  -> unit eur: 1.00\n  -> decimals 2\n\nrule tax_amount:\n  price * tax_rate\n\nrule price_with_tax:\n  price + tax_amount\n\nrule discount_amount:\n  price * standard_discount\n\nrule discounted_price:\n  price - discount_amount\n\nrule final_price:\n  discounted_price * (100% + tax_rate)\n\n\nspec line_item\n\nuses pricing: base_config\n  -> with price: 10 eur\n\ndata qty: number\n  -> minimum 0\n  -> suggest 10\n\n\nrule line_total:\n  pricing.final_price * qty\n\nrule has_discount:\n  pricing.standard_discount > 0%\n```\n\n- `uses alias: target_spec`: imports spec in same file.\n- Reference members: `alias.field` or `alias.rule_name`.\n- Under `uses`, `  -> with path: value` sets imported data (path relative to imported spec). Do not use `data alias.field`. Standalone `with alias.field: \u{2026}` is deprecated (still parses; `quality` recommends block form). Local slots use `data`.\n\n**LemmaBase: shared repositories**\n\nRepositories on [LemmaBase.com](https://lemmabase.com) imported with `@` repo qualifiers. Search: [lemmabase.com/search?q=](https://lemmabase.com/search?q=) (e.g. `?q=finance`).\n\n```lemma\nspec invoicing\n\nuses lemma units\n\nuses iso: @iso/countries alpha2 2026-01-01\n\ndata price: measure\n  -> unit eur: 1\n\ndata country: iso.code\n\n\nrule tariff:\n  0 eur\n  unless country is \"NL\" then price * 5%\n\nrule total:\n  price + tariff\n```\n\nForms:\n- `uses @user/repo spec_name`: import LemmaBase spec (alias = spec name)\n- `uses alias: @user/repo spec_name`: import with alias (`iso.field`)\n- `uses @user/repo spec_name 2026-01-01`: pin effective date\n\nReference imported members: `iso.code`. Detail: [LemmaBase](https://lemma.run/reference/registry).\n\n`repo` blocks namespace specs across contexts (e.g., `repo accounting`). Skip unless asked. Details: [Composing specs](https://lemma.run/learn/composing_specs).\n\n\n---\n\n**Data: spoken question, not precomputed answer**\n\n`data` declares variables. Constraints define validity. Type-only `data` (no value) is an input slot. Use real domain values. Never `\"TODO\"` or dummy placeholders.\n\n**Spoken question**\n\nEach `data` field\'s `-> help` is the sentence you would say to a person about their situation. If that sentence requires counting, converting, applying a legal threshold, or interpreting the policy, the field is wrong: those are `rule`s. Dates, amounts, and yes/no facts of the situation stay as `data`.\n\nWRONG (elapsed time as a bare number; unit baked into the name):\n```lemma-skip\ndata days_overdue: number\n  -> help \"How many days late is the book?\"\n```\n\nRIGHT (facts of the situation; duration keeps its unit):\n```lemma\nspec library_return\n\nuses lemma units\n\ndata due_date: date\n  -> help \"When was the book due?\"\n\ndata return_date: date\n  -> help \"When was the book returned?\"\n\n\nrule overdue:\n  due_date...return_date as day\n```\n\nName the concept; keep the unit on the value, not in the field name (`see **Units**`).\n\nWRONG (policy threshold already applied):\n```lemma-skip\ndata qualifies_for_free_shipping: boolean\n  -> help \"Is the order \u{20ac}50 or more?\"\n```\n\nRIGHT:\n```lemma\nspec shipping_threshold\n\ndata money: measure\n  -> unit eur: 1.00\n  -> decimals 2\n\ndata order_total: money\n  -> help \"What is the order total?\"\n\n\nrule free_shipping:\n  order_total >= 50 eur\n```\n\nAge-in-years can stay when that is what you would actually ask (e.g. senior discount). A birthday gate wants a birth date.\n\n**Measure**\n\nUse `measure` when the fact carries a unit: money, mass, distance, duration, rates, energy, and other dimensional quantities. Use `number` only when the value is truly dimensionless (counts, scores, IDs). Use `ratio` for proportions (`15%`, `50 permille`), not for currency amounts.\n\nDeclare a **named measure parent** once, then extend it on input slots:\n\n```lemma\nspec invoice\ndata money: measure\n  -> unit eur: 1.00\n  -> decimals 2\n  -> minimum 0 eur\n\ndata invoice_total: money\n  -> help \"What is the invoice total?\"\n```\n\n`-> unit` rows define which units the type accepts; literals and comparisons use those units (`50 eur`, not a bare `50`). Name the concept (`invoice_total`, `package_weight`), not the unit in the field name (`price_eur`, `weight_kg`).\n\nFor physical quantities and time, import the standard library (`uses lemma units`) and prefer `units.mass`, `units.duration`, `units.length` over redefining SI units. Duration literals (`8 hour`, `25 year`), conversion (`as <unit>`), compound units (`eur/hour`), measure ranges, and calendar intervals are all covered in **Units**.\n\n**Boolean naming**\n\nName booleans for the fact you mean; prefer the natural predicate with `-> suggest` for the usual case (`data item_damaged: boolean -> suggest false`). Do not invent awkward opposites (`item_undamaged`) or negated names (`not_*`, `no_*`, `has_no_*`).\n\n**Constraints**\n\n**Types**\n\nPrimitives: `boolean`, `number`, `text`, `measure`, `ratio`, `date`, `time`, and `number range`, `measure range`, `date range`, `time range`, `ratio range`. Named parents (`data money: measure`) inherit constraints; see [Reference](https://lemma.run/reference#extending-data).\n\n**Data commands by type**\n\n| Type | `->` commands |\n|------|----------------|\n| `boolean` | `help`, `fill`, `suggest` |\n| `number` | `decimals`, `minimum`, `maximum`, `help`, `fill`, `suggest` |\n| `measure` | `unit`, `decimals`, `minimum`, `maximum`, `help`, `fill`, `suggest` (see **Measure**, **Units**) |\n| `ratio` | `unit`, `minimum`, `maximum`, `help`, `fill`, `suggest` |\n| `text` | `option`, `options`, `length`, `help`, `fill`, `suggest` |\n| `date`, `time` | `minimum`, `maximum`, `help`, `fill`, `suggest` |\n| `number range`, `date range`, `time range`, `ratio range` | `lower`, `upper`, `minimum`, `maximum`, `help`, `fill`, `suggest` |\n| `measure range` | above + `unit` |\n\nOn range types, `lower` / `upper` bound endpoints; `minimum` / `maximum` bound **span width**, not endpoints.\n\n**Usage**\n\n- Chain `->` rows on `data` only (rules have no constraints; see **Syntax**).\n- `-> help` = spoken question (CS ask string).\n- `-> fill` = commit a value on a typed slot (callers may override); cannot combine with `suggest` on the same declaration.\n- `-> suggest` = UI hint; does not fill or commit.\n- Prefer `-> option` for closed text sets.\n- Bounded domain on constraints (`-> minimum 0`), not veto (see **Veto**).\n- Examples: type catalog [01_coffee_order.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/01_coffee_order.lemma); fee policy [02_library_fees.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/02_library_fees.lemma).\n- Edge cases (compound units, qualifying units, calendar ranges): **Units** and [Reference](https://lemma.run/reference#data-commands).\n\n\n---\n\n**Standard library: `uses lemma units`**\n\nLemma embeds SI bases, derived compounds (force, pressure, energy, power, frequency, electrical), imperial, area/volume, and information (`bit`/`byte`) in `repo lemma` / `spec units`. Import: `uses lemma units`. Reference types: `units.mass`, `units.duration`, `units.length`, `units.force`. Unit names: **singular only** (`8 hour`). Length uses American `meter`. After `uses lemma units`, duration literals (`hour`, `day`, `week`) work; `units.duration` is the type name when you declare a duration slot. No Celsius/Fahrenheit (kelvin only).\n\n```lemma\nspec logistics\n\"\"\"\nPhysical shipment constraints using SI units from the standard library.\n\"\"\"\n\nuses lemma units\n\ndata package_weight: 12 kilogram\ndata shift_length:   8 hour\ndata route_distance: 45 kilometer\n\n\nrule is_heavy:\n  package_weight > 20 kilogram\n\nrule is_long_shift:\n  shift_length >= 8 hour\n```\n\nPrefer `units.mass`, `units.duration`, `units.length` over redefining units. Convert in family: `as <unit>`. Strip unit: `amount as eur as number`. Cross-family relabel: `5 eur as kg` -> `5 kg`. Name the concept, not the unit (see **Anti-patterns**).\n\n**Ranges: half-open intervals**\n\nRanges: lower bound inclusive, upper bound exclusive (`lo...hi`). Test with `in`. Width: `lo...hi as <unit>` (duration/measure). Add `as number` only when a bare number is required. Bare `as number` on date/measure ranges fails. Typedefs: `number range`, `date range`, `measure range`, `ratio range`. Month/year intervals: `uses lemma units` and inline literals (`18 year...67 year`) or `units.calendar range`.\n\nThe snippet below is a syntax sample, not one policy.\n\nWorking age (calendar range):\n```lemma\nspec working_age\n\nuses lemma units\n\ndata employee_age: 42 year\ndata eligible_band: units.calendar range\n  -> suggest 18 year...67 year\n\n\nrule is_working_age:\n  employee_age in eligible_band\n```\n\nUpper bound exclusive: `67 year` is NOT inside `18 year...67 year`.\n\nCustom measure types can declare their own `measure range` without importing SI; see Reference.\n\n**Derived measures: compound units**\n\nBuild compound units with `/`, `*`, `^`. Name derived unit, then give compound expression. Prior measure types must declare referenced base units. Import `uses lemma units` if using time (`eur/hour`).\n\n```lemma\nspec contractor\n\nuses lemma units\n\ndata money: measure\n  -> unit eur: 1.00\n\ndata wage_rate: measure\n  -> unit eur_per_hour: eur/hour\n\ndata time_worked: 120 hour\ndata wage: wage_rate\n  -> suggest 85 eur_per_hour\n\n\nrule total:\n  wage * time_worked\n```\n\nLayer compound units: `eur_per_hour` builds on `eur` and `hour`. Dimensional checks run at plan time.\n\n**Date predicates relative to `now`**\n\n`now` is evaluation/effective instant. Import `uses lemma units` for duration windows.\n\n| Form | Meaning |\n|------|---------|\n| `date in past` / `in future` | Before / after `now` |\n| `date in past N day` / `in future N day` | In last / next N duration units |\n| `past N day` / `future N day` | Relative date-range window |\n| `date in calendar year\\|month\\|week` | Current calendar period |\n| `date in past\\|future calendar year\\|month\\|week` | Adjacent calendar period |\n| `date not in calendar year\\|month\\|week` | Not current calendar period |\n\n```lemma\nspec recency\n\nuses lemma units\n\ndata event_date: date\n  -> help \"When did the event happen?\"\n\n\nrule recent:\n  event_date in past 7 day\n\nrule this_year:\n  event_date in calendar year\n```\n\n\n---\n\n**Rules and unless: last matching clause wins**\n\nDefault expression, then `unless <condition> then <result>`. Source order; **bottommost match wins**. General first, specific last. Snake_case names; boolean predicates (`is_eligible`, `can_ship`). Named pipeline rules, no opaque mega-expressions.\n\n**Domain-principle default**\n\nDefault is not \"prefer yes/no.\" It is the answer **in principle for this rule\'s domain**. Experts must read top-to-bottom as: \"In principle X; unless Y, then Z.\"\n\n1. Name the question (`can_ship` \u{2192} \"Can we ship this order?\").\n2. Before special cases, what is true in principle? That is the default: from *this* rule\'s domain, not optimism or \"start true and subtract failures.\"\n3. What positive facts change the answer? Those are `unless` conditions, not negated flips (`unless not ready then no`).\n4. Write `rule name: <principle> unless <positive conditions> then <exception>`.\n\nExamples by domain: shipping often earned (`no` unless grant); discount often `0%`; fees use the policy\'s usual fee, not \"free unless expensive.\"\n\nForbidden: double denial (`yes` / `unless not \u{2026} then no`); fail-each-check cascades; invented `*_compliant` default-yes helpers; a single `no` / `unless \u{2026} then yes` when the condition is the whole answer (see **Anti-patterns**). In `and` chains, do not mix a bare name with `not` / `is false` (WRONG: `x and not y`); use parallel probes (`not x and y is true`, or `x is false and y is true`) or parentheses. Unary `not x` alone is fine.\n\n**Overlapping unless (last wins)**\n\n```lemma\nspec vip_discount\n\ndata qty: number\n  -> minimum 0\n  -> help \"How many items?\"\n\ndata is_vip: boolean\n  -> suggest false\n  -> help \"Is the customer a VIP?\"\n\n\nrule discount:\n  0%\n  unless qty >= 10 then 10%\n  unless qty >= 50 then 20%\n  unless is_vip    then 25%\n```\n\nVIP ordering 75 items gets **25%** (not 20%): both `qty >= 50` and `is_vip` match; bottommost wins.\n\n\n---\n\n**Veto: impossible to answer, not `false`**\n\nVeto is like Rust\'s `Err`: rule has **no value** and propagates to dependents. Use veto when the question has no answer, not when the business answer is false, and not for out-of-range inputs.\n\n| Situation | Use |\n|-----------|-----|\n| Out-of-range (negative score, age above 120) | `-> minimum` / `-> maximum` on `data` |\n| Closed choice list | `-> option` on `data` |\n| Normal business \"no\" | `false` or `no` |\n| Lookup / no mapped result | default `veto` + `unless` arm per known case |\n| Test veto without propagating | `x is veto` (returns boolean) |\n\n**Litmus test:** Can the question be answered? If yes, even when the answer is negative, use `true`/`false`. If the question itself is unanswerable for this input, use veto. \"Is the customer eligible?\" is always answerable. \"What is the price of this product?\" when it is not on the list is unanswerable (veto). Out-of-range age is `-> maximum 120`, not veto.\n\nA vetoed rule is not `false`. `x is false` does not match a vetoed `x`. To test whether a rule vetoed, use `x is veto`.\n\n**Lookup default vs unless veto**\n\nLookup: the default expression is `veto \"\u{2026}\"`; each `unless` arm maps a known case. `-> option` is the intake set; default `veto` is the mapping when a listed value has no price arm.\n\nAn `unless \u{2026} then veto` arm (business denial, not lookup) follows last-wins like any other `unless` (see **Rules**).\n\n**Lookup (normal)**\n\n```lemma\nspec coffee_pricing\n\ndata money: measure\n  -> unit eur: 1.00\n  -> decimals 2\n\ndata product: text\n  -> option \"espresso\"\n  -> option \"latte\"\n  -> option \"cappuccino\"\n  -> option \"mocha\"\n\n\nrule base_price:\n  veto \"Unknown type of coffee\"\n  unless product is \"espresso\"   then 2.5 eur\n  unless product is \"latte\"      then 3.5 eur\n  unless product is \"cappuccino\" then 3.5 eur\n  unless product is \"mocha\"      then 4 eur\n```\n\nIf `base_price` vetoes, dependents that need its value veto too (propagates). Full example: [01_coffee_order.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/01_coffee_order.lemma).\n\n**Veto vs boolean vs constraint**\n\nWRONG: veto for business denial or range check:\n```lemma-skip\nrule is_eligible:\n  true\n  unless customer_age < 18  then veto \"Must be 18+\"\n  unless customer_age > 120 then veto \"Invalid age\"\n```\n\nRIGHT: bounds on data; boolean for business no:\n```lemma\nspec age_gate\n\ndata customer_age: number\n  -> minimum 0\n  -> maximum 120\n  -> help \"How old is the customer?\"\n\n\nrule is_adult:\n  customer_age >= 18\n```\n\n\n---\n\n**Anti-patterns**\n\nInline comments (WRONG: `#` fails to parse): `data customer_age: number -> minimum 0  # input`, `rule discount: 0%  # default`.\nRIGHT: no inline comments; commentary after `spec` only (see **Syntax**).\n\nCommentary after `uses` (WRONG). RIGHT: commentary immediately after `spec` (see **Syntax**).\n\nMega-rule (WRONG): opaque nested `unless` inside one expression. RIGHT: named pipeline rules (see **Rules**).\n\nHardcoded input (WRONG): `rule discount: 10 * 0.1`\nRIGHT: `data qty: number` then `rule discount: qty * 0.1`\n\nWrong unless order (WRONG: VIP gets 20% not 25%):\n```lemma-skip\nrule discount:\n  0%\n  unless is_vip    then 25%\n  unless qty >= 50 then 20%\n```\nRIGHT: specific override last (`qty` tiers first, `is_vip` last; see **Rules**).\n\nPlaceholder (WRONG): `data customer_name: \"TODO\"`\nRIGHT: `data customer_name: text`\n\nElapsed time / pre-interpreted threshold as data (WRONG):\n```lemma-skip\ndata days_overdue:                number\ndata qualifies_for_free_shipping: boolean\n```\nRIGHT: dates and amounts as data; duration/threshold rules without unit names (see **Data**).\n\nUnit in the name (WRONG \u{2014} common agent failure):\n```lemma-skip\nrule days_overdue:\n  due_date...return_date as day as number\n\nrule weight_kg:\n  package_weight as kilogram\n\nrule price_eur:\n  total\n```\nRIGHT: name the concept; keep the unit on the value (see **Data**, **Units**).\n\nVeto for bounds (WRONG):\n```lemma-skip\nrule validated_score:\n  score\n  unless score < 0 then veto \"Invalid score\"\n```\nRIGHT: `data score: number -> minimum 0`. Veto is unanswerable, not out-of-range (see **Veto**).\n\nError vs Veto: `5 and \"text\"` = planning Error. Unmapped product with default `veto \"\u{2026}\"` = runtime Veto.\n\nVeto-as-rejection (WRONG: denial is answerable; see **Veto**):\n```lemma-skip\nrule is_eligible:\n  true\n  unless age < 18   then veto \"Must be 18+\"\n  unless not has_id then veto \"ID required\"\n```\nRIGHT: boolean rules (`is_adult` and `has_valid_id`).\n\nUnnecessary `repo` (WRONG). RIGHT: single-file `spec` without `repo`.\n\nNo `or` (WRONG): `rule is_eligible: is_adult or has_guardian`. See **Syntax**; use `unless` or separate booleans.\n\nConstraints on rules (WRONG): `rule discount: 10% -> help \"\u{2026}\"`. `->` is data-only (see **Syntax**).\n\nDomain-blind polarity / double denial (WRONG):\n```lemma-skip\nrule can_ship:\n  yes\n  unless not in_stock         then no\n  unless not address_complete then no\n```\nRIGHT: domain principle + positive grant (see **Rules**).\n\nUseless default/unless for boolean rules (WRONG):\n```lemma-skip\nrule can_ship:\n  no\n  unless in_stock and address_complete then yes\n```\nRIGHT: `rule can_ship: in_stock and address_complete`. When one `unless \u{2026} then yes` is the whole answer, write the condition directly; `unless` is for exceptions, not a boolean wrapper.\n\n---\n\n## See also\n\n- [Learn guide](https://lemma.run/learn)\n- [Reference](https://lemma.run/reference)\n- [LemmaBase search](https://lemmabase.com/search?q=)\n- [Examples](https://github.com/lemma/lemma/tree/main/engine/documentation/examples)\n  - [01_coffee_order.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/01_coffee_order.lemma)\n  - [02_library_fees.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/02_library_fees.lemma)\n  - [03_recipe_scaling.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/03_recipe_scaling.lemma)\n  - [04_membership_benefits.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/04_membership_benefits.lemma)\n  - [05_weather_clothing.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/05_weather_clothing.lemma)\n  - [nl/tax/net_salary.lemma](https://raw.githubusercontent.com/lemma/lemma/main/engine/documentation/examples/nl/tax/net_salary.lemma)\n\nDecimals at JSON boundaries: pass as strings. Detail: [Numeric precision](https://lemma.run/learn/precision).\n";