lemma 0.9.2

A pure, declarative language for business rules.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
# Lemma

> Lemma: declarative business rule language. Translate natural-language policy to readable, deterministic `.lemma` specs. Inputs derived from rules — declare `data` with constraints. No runtime value invention.

Goal: produce Lemma source for human reading and system evaluation. Prefer named pipeline rules. No clever one-liners. Split unrelated policies to separate specs.
Use `lemma show` to inspect static interface (types, constraints, normalized rules). Run-data needs come from rule `missing_data` on `run` (not `show`).
Explanations opt-in (`explain: true` / `lemma run -x`). Wire shape: `engine/schemas/api.v1.json` (`RuleResult.explanation` is `RuleNode`; nested nodes under `ExplanationNode`).

**NO INLINE COMMENTS.** Lemma has no `#`, `//`, or `--` comment syntax. `#` starts parse error. No trailing remarks on `data` or `rule` lines.

**Only doc in source:** commentary block `"""..."""`. Must be **very next tokens** after `spec` line (before `uses`, `data`, etc.). Nowhere else.


---

**Mandatory spec opening order:**

```
spec <name> [<effective>]
[""" commentary — optional, but if present must be HERE """]
uses ...
data ...
rule ...
```

Commentary after `uses` or `data` is invalid, parse fails. No `#`, `//`, `--` comment syntax. Use descriptive names. Put user explanations outside code fences — never inside ` ```lemma ` blocks.


---

**Organization: spec → rule**

Default: one file, one implicit repo. No `repo` blocks unless multi-namespace workspace requested. Structure: **spec → rule**.

**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.
Hierarchical names: `spec employee/contract`. Effective date for temporal changes: `spec pricing 2026-01-01`.
Sole documentation: commentary `"""..."""` immediately after `spec <name> [<effective>]` (before `uses`, `data`, or `rule`).

**Example A — minimal single-spec file**

```lemma
spec pricing 2026-01-01
"""
Pricing rules for bulk and member discounts.
Commentary must follow the spec line; it cannot go anywhere else.
"""

data qty:        number
data base_price: 100
data is_member:  false

rule vat_amount: base_price * 21%
rule price_with_vat: base_price + vat_amount

rule bulk_discount:
  qty >= 100 and price_with_vat > 500

rule discount: 0%
  unless qty >= 10     then 10%
  unless bulk_discount then 15%
  unless is_member     then 20%

rule discount_amount: base_price * discount
rule price_with_discount: base_price - discount_amount
```

**Example B — multi-spec composition (same file)**

```lemma
spec base_config

data standard_discount: 5%
data tax_rate:          21%
data base_price:        number -> minimum 0 -> suggest 100

rule tax_amount:       base_price * tax_rate
rule price_with_tax:   base_price + tax_amount
rule discount_amount:  base_price * standard_discount
rule discounted_price: base_price - discount_amount
rule final_price:      discounted_price * (100% + tax_rate)


spec line_item

data qty: number -> minimum 0 -> suggest 10

uses pricing: base_config

rule line_total:   pricing.final_price * qty
rule has_discount: pricing.standard_discount > 0%


spec simple_order

uses line: line_item
with line.qty: 100

rule order_total:          line.line_total
rule effective_unit_price: order_total / line.qty
```

- `uses alias: target_spec`: imports spec in same file.
- Reference members: `alias.field` or `alias.rule_name`.
- `with alias.field: value`: sets imported data. Do not use `data alias.field`. Local `with name: …` invalid — use `data name: …`.

**LemmaBase — shared specs from the registry**

Specs on [LemmaBase.com](https://lemmabase.com) imported with `@` repo qualifiers. Search: [lemmabase.com/search?q=](https://lemmabase.com/search?q=) (e.g. `?q=finance`).

```lemma
spec invoicing
"""
Invoice lines using ISO country codes from LemmaBase.
"""

uses lemma units

uses iso: @iso/countries alpha2 2026-01-01

data price: measure
  -> unit eur 1

data country: iso.code

rule tariff: 0 eur
  unless country is "NL" then price * 5%

rule total: price + tariff
```

Forms:
- `uses @user/repo spec_name`: import registry spec (alias = spec name)
- `uses alias: @user/repo spec_name`: import with alias (`iso.field`)
- `uses @user/repo spec_name 2026-01-01`: pin effective date

Reference imported members: `iso.code`. Detail: [Registry](https://github.com/lemma/lemma/blob/main/cli/documentation/reference/registry.md).

`repo` blocks namespace specs across contexts (e.g., `repo accounting`). Skip unless asked. Details: [Composing specs](https://github.com/lemma/lemma/blob/main/cli/documentation/learn/composing_specs.md).


---

**Natural language → Lemma**

Request: *"Library charges €0.25/day for regular books, €0.50 for reference, €1 for new releases. First offense gets 50% off. Grace period 3 day except new releases. Block checkout if fee exceeds €10."*

Map logic:
- Book types → `data book_type` with `-> option` constraints
- Overdue days, first offense → `data` input slots
- Per-day rates → `rule daily_fee` with unless branches
- Grace period → `rule is_in_grace_period`
- Fee pipeline → `rule total_fee`, `rule final_fee`
- Checkout block → `rule can_checkout: yes` with `unless final_fee > 10 eur then no` (boolean, not veto)

**Example C — library fees (full spec)**

```lemma
spec library_fees

data money: measure
  -> decimals 2
  -> unit eur 1.00
  -> minimum 0 eur

data book_kind: text
  -> option "regular"
  -> option "reference"
  -> option "new_release"

data book_type:        book_kind
data is_first_offense: boolean
data days_overdue: number
  -> minimum 0

rule daily_fee: 0 eur
  unless book_type is "regular"     then 0.25 eur
  unless book_type is "reference"   then 0.5 eur
  unless book_type is "new_release" then 1 eur

rule is_in_grace_period: days_overdue <= 3
  unless book_type is "new_release" then no

rule total_fee: days_overdue * daily_fee

rule final_fee: total_fee
  unless is_first_offense   then total_fee * 50%
  unless is_in_grace_period then 0 eur

rule can_checkout: yes
  unless final_fee > 10 eur then no
```


---

**Data: constraint definitions, not placeholders**

`data` declares variables. Constraints define validity. Type-only `data` (no value) is input slot. `lemma show` lists static reachable data. Evaluator needs come from rule `missing_data` on `run` (not `show`). Use real domain values. Never `"TODO"` or dummy placeholders.

**Example D — typed data (coffee order)**

```lemma
spec coffee_order

data money: measure
  -> decimals 2
  -> unit eur 1.00
  -> unit gbp 1.17
  -> unit usd 0.84
  -> minimum 0 eur

data product: text
  -> option "espresso"
  -> option "latte"
  -> option "cappuccino"
  -> option "mocha"

data size: text
  -> option "small"
  -> option "medium"
  -> option "large"

data age: number
  -> maximum 100
  -> minimum 0

data number_of_cups: number
  -> maximum 10

data has_loyalty_card: boolean
```

- `age`, `number_of_cups`: input slots (type-only + bounds)
- `money`: custom measure type with units, decimals, minimum
- `product`, `size`: text enumeration via `-> option` (prefer over veto for static sets)

**Example E — data patterns**

Input slot:
```lemma
spec intake

data customer_age: number -> minimum 0 -> maximum 120
```

Fixed policy constant:
```lemma
spec fiscal_policy

data tax_rate: 21%
```

Text enumeration:
```lemma
spec membership

data status: text
  -> option "active"
  -> option "inactive"
```

Typed alias:
```lemma
spec accounts

data money: measure -> unit eur 1.00

data wallet: money -> minimum 0 eur
```

With help text:
```lemma
spec payroll

data pay_period: text
  -> option "month"
  -> option "week"
  -> help "How often you are paid."
```

Constraints chain: `-> minimum`, `-> maximum`, `-> option`, `-> unit`, `-> decimals`, `-> suggest`, `-> help`, etc. Details in Reference.


---

**Standard library — `uses lemma units`**

Lemma 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`. Durations (`hour`, `day`, `week`) require `units.duration`. No Celsius/Fahrenheit (kelvin only).

```lemma
spec logistics
"""
Physical shipment constraints using SI units from the standard library.
"""

uses lemma units

data package_weight: 12 kilogram
data shift_length:   8 hour
data route_distance: 45 kilometer

rule weight_grams:  package_weight as gram
rule shift_hours:   shift_length as hour
rule distance_km:   route_distance as kilometer

rule is_heavy:      package_weight > 20 kilogram
rule is_long_shift: shift_length >= 8 hour
```

Prefer `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`.

**Ranges — half-open intervals**

Ranges: lower bound inclusive, upper bound exclusive (`lo...hi`). Test with `in`. Width: `(lo...hi) as <unit> as number` (or `(lo...hi) as <unit>`). 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`.

```lemma
spec eligibility
uses lemma units

data employee_age:       42 year
data performance_score:  75
data package_weight:     45 kilogram
data hire_date:          2024-01-15
data review_date:        2024-06-30
data discount_rate:      15%

data eligible_band: units.calendar range
  -> suggest 18 year...67 year

data score_band: number range
  -> suggest 0...100

rule is_working_age: employee_age in eligible_band

rule is_top_score: performance_score in 90...100

rule is_heavy: package_weight in 30 kilogram...80 kilogram

rule in_discount_band: discount_rate in 0%...50%

rule in_q2: hire_date in 2024-04-01...2024-07-01

rule review_days: (hire_date...review_date) as day

rule span_years: (1990-05-20...2024-06-15) as year
```

Upper bound exclusive: `67 year` is NOT inside `18 year...67 year` (returns false). Declare range slots on `data` for reuse, inline `value in lo...hi` for one-off.

Range over custom measure type (no SI import needed):

```lemma
spec freight

data weight: measure
  -> unit gram 1
  -> unit kilogram 1000

data load_band: weight range
  -> suggest 30 kilogram...80 kilogram

rule inside_band: 45 kilogram in load_band

rule band_width: (30 kilogram...80 kilogram) as kilogram
```

**Derived measures — compound units**

Build compound units with `/`, `*`, `^`. Name derived unit, then give compound expression. Prior measure types must declare referenced base units (`eur`, `hour`, `employee`, `results`). Import `uses lemma units` if using time (`eur/hour`).

```lemma
spec contractor
uses lemma units

data money: measure
  -> unit eur 1.00

data headcount: measure
  -> unit employee 1

data outcome: measure
  -> unit results 1

data wage_rate: measure
  -> unit eur_per_second eur/second
  -> unit eur_per_hour eur/hour

data productivity: measure
  -> unit result_per_employee results/eur/hour/employee

data premium_per_head: measure
  -> unit eur_hour_per_employee eur_per_hour/employee

data time_worked: 120 hour
data wage: wage_rate -> suggest 85 eur_per_hour
data yield_rate: productivity -> suggest 3 result_per_employee

rule total: wage * time_worked

rule is_high_yield: yield_rate >= 2 result_per_employee
```

Layer compound units: `eur_per_hour` builds on `eur` and `hour`. Dimensional checks run at plan time.

**Date predicates relative to `now`**

`now` is evaluation/effective instant. Import `uses lemma units` for duration windows.

| Form | Meaning |
|------|---------|
| `date in past` / `in future` | Before / after `now` |
| `date in past N day` / `in future N day` | In last / next N duration units |
| `past N day` / `future N day` | Relative date-range window |
| `date in calendar year\|month\|week` | Current calendar period |
| `date in past\|future calendar year\|month\|week` | Adjacent calendar period |
| `date not in calendar year\|month\|week` | Not current calendar period |

```lemma
spec recency
uses lemma units
data event_date: date
rule recent: event_date in past 7 day
rule this_year: event_date in calendar year
```


---

**Rules and unless: last matching clause wins**

Write default expression, then: `unless <condition> then <result>`. Evaluated in source order. If multiple match, **bottommost wins**. Order general first, specific overrides last.

Use **snake_case** rule names. Boolean rules as predicates: `is_eligible`, `can_ship`. Decompose logic into pipeline of named rules. No opaque single expressions.

**Example F — overlapping unless (last wins)**

```lemma
spec vip_discount

data qty: number
data is_vip:   boolean

rule discount: 0%
  unless qty >= 10  then 10%
  unless qty >= 50  then 20%
  unless is_vip          then 25%
```

VIP customer ordering 75 items gets **25%** (not 20%). Both `qty >= 50` and `is_vip` match; bottommost wins.

**Example G — progressive unless chain**

```lemma
spec rules_and_unless

data is_premium:   yes
data base_price:   number -> minimum 0
data customer_age: number -> minimum 0 -> maximum 120
data qty:          number -> minimum 0

rule total_before_discount:
  base_price * qty

rule discount_percentage:
  0%
  unless qty >= 10 then 10%
  unless qty >= 20 then 15%
  unless is_premium   then 20%

rule discount_amount:
  total_before_discount * discount_percentage

rule total_after_discount:
  total_before_discount - discount_amount

rule shipping_cost:
  15
  unless total_after_discount >= 100 then 10
  unless total_after_discount >= 200 then 0

rule final_total:
  total_after_discount + shipping_cost
```

Tiered discounts, derived rules referencing prior rules, unless on computed values.

**Example H — decomposed shipping pipeline**

```lemma
spec shipping_policy
uses lemma units

data destination_country: text
  -> option "NL"
  -> option "BE"
  -> option "DE"
  -> option "FR"
  -> suggest "NL"

data customer_tier: text
  -> option "standard"
  -> option "silver"
  -> option "gold"
  -> option "platinum"
  -> suggest "gold"

data destination_region: text
data is_expedited:       boolean
data is_hazardous:       boolean
data is_po_box:          boolean
data item_weight:        units.mass
data order_total:        number -> minimum 0

rule base_shipping_rate: 35
  unless destination_country is "NL" then 22
  unless destination_country is "BE" then 25
  unless destination_country is "DE" then 28
  unless destination_country is "FR" then 30

rule weight_surcharge: 0
  unless item_weight > 5 kilogram  then 7.5
  unless item_weight > 20 kilogram
    then veto "Item too heavy for standard shipping"

rule po_box_fee: 0
  unless is_po_box then 5

rule expedited_fee: 0
  unless is_expedited                           then 25
  unless is_expedited and item_weight > 10 kilogram then 45

rule hazardous_fee: 0
  unless is_hazardous then 50
  unless is_hazardous and destination_country is not "NL"
    then veto "Cannot ship hazardous materials internationally"

rule customer_discount: 0%
  unless customer_tier is "silver"   then 10%
  unless customer_tier is "gold"     then 20%
  unless customer_tier is "platinum" then 30%

rule free_shipping_eligible:
  order_total >= 100 and destination_country is "NL"

rule shipping_before_discount:
  base_shipping_rate + weight_surcharge + po_box_fee
  + expedited_fee + hazardous_fee

rule shipping_discount_amount:
  shipping_before_discount * customer_discount

rule final_shipping:
  shipping_before_discount - shipping_discount_amount
  unless free_shipping_eligible then 0

rule ships_to_location: true
  unless is_po_box and is_hazardous
    then veto "Cannot ship hazardous materials to PO boxes"
  unless destination_region is "Svalbard"
    then veto "Shipping not available to Svalbard"

rule Summary: "Standard shipping"
  unless free_shipping_eligible
    then "Free shipping (order over €100)"
  unless is_expedited then "Expedited shipping"
```

Each fee is distinct rule. `item_weight` uses `units.mass`. Veto clauses placed last in `weight_surcharge` and `ships_to_location`. Eligibility separated from amount rules.


---

**Veto: impossible to answer, not `false`**

Veto is like Rust's `Err` — rule has **no value** and propagates to dependents. Use veto when answer is impossible, not when business answer is false.

| Situation | Use |
|-----------|-----|
| Invalid/out-of-domain input | `unless ... then veto "reason"` |
| Unmapped choice / no rule applies | default `veto` + unless arm per choice |
| Normal business "no" | `false` or `no` |
| Test veto without propagating | `x is veto` (returns boolean) |

Place veto unless clauses **last** to override other branches.

**Example I — enumeration with default veto**

```lemma
spec choice_mapping

data choice_field: number

rule selected: veto
  unless choice_field is 1 then true
  unless choice_field is 2 then false
  unless choice_field is 3 then true
```

Default `veto` if unlisted. Each `unless` maps known choice. `false` is valid answer for choice 2.

**Example J — veto lookup + propagation**

```lemma
spec coffee_pricing

data money: measure
  -> unit eur 1.00
  -> decimals 2

data product: text
data size:    text

rule base_price: veto "Unknown type of coffee"
  unless product is "espresso"   then 2.5 eur
  unless product is "latte"      then 3.5 eur
  unless product is "cappuccino" then 3.5 eur
  unless product is "mocha"      then 4 eur

rule size_multiplier: veto "Unknown size of coffee"
  unless size is "small"  then 80%
  unless size is "medium" then 100%
  unless size is "large"  then 120%

rule price_per_cup: base_price * size_multiplier
```

If `base_price` vetoes, `price_per_cup` vetoes automatically (propagates).

**Example K — veto vs boolean**

WRONG — veto for business decision:
```lemma-skip
rule can_checkout: veto
  unless fee <= 10 eur then true
```

RIGHT — veto for invalid input, boolean for business logic:
```lemma
spec checkout_policy

data money: measure
  -> unit eur 1.00
  -> decimals 2

data customer_age: number
data fee:          money

rule age_validation:
  true
  unless customer_age < 18  then veto "Customer must be 18 or older"
  unless customer_age > 120 then veto "Invalid age"

rule can_checkout: yes
  unless customer_age < 18 then no
  unless fee > 10 eur then no
```

**Example L — veto propagation with unless fallback**

```lemma
spec scoring

data score:       number
data use_default: boolean

rule validated_score: score
  unless score < 0 then veto "Invalid score"

rule result: validated_score
  unless use_default then 50
```

If `validated_score` vetoes but `use_default` is true, `result` is 50. Unless branch avoids needing vetoed value.

**Workflow checklist**

1. **Scope**: one spec per coherent policy; compose with `uses`; skip `repo` unless needed.
2. **Inputs**: every user-supplied fact is `data` with constraints.
3. **Outputs**: every answerable question is a `rule`.
4. **Factor**: intermediate calculations as named rules.
5. **Unless**: default first, general to specific, vetoes last.
6. **No inline comments**: no `#`, `//`. Commentary `"""..."""` only as first block after `spec`.
7. **Validate**: verify reachable data with `show`, missing inputs via `run`, and correct override order.
8. **Advanced**: use `uses lemma units`, ranges, compound units when needed.


---

**Anti-patterns**

Inline comments (WRONG — `#` fails to parse):
```lemma-skip
data customer_age: number -> minimum 0 -> maximum 120  # input slot
data tax_rate: 21%  # fixed policy constant
rule discount: 0%  # default: no discount
  unless qty >= 10 then 10%  # bulk rate
```

Commentary after `uses` (WRONG — fails to parse):
```lemma-skip
spec wholesale_nuts_pricing
uses lemma units
"""
B2B pricing calculation for a wholesale nuts supplier.
Handles product base pricing, organic premiums, and freight shipping.
"""

data base_price: number
```

Commentary after `data` (WRONG — fails to parse):
```lemma-skip
spec pricing

data qty: number
"""
This is not valid commentary placement.
"""

rule discount: 0%
```

Commentary before `uses` (RIGHT):
```lemma
spec wholesale_nuts_pricing
"""
B2B pricing for a wholesale nuts supplier.
Handles base pricing, organic premiums, volume discounts, and freight.
"""

uses lemma units

data base_price: number
```

Commentary immediately after spec (RIGHT — no `uses`):
```lemma
spec pricing
"""
Customer age is an input; tax_rate is a fixed policy constant.
"""

data customer_age: number -> minimum 0 -> maximum 120
data tax_rate: 21%
data qty: number

rule discount: 0%
  unless qty >= 10 then 10%
```

Mega-rule (WRONG):
```lemma-skip
rule final_total:
  base_price * qty
  - base_price * qty * (0% unless qty >= 10 then 10% unless is_premium then 20%)
  + (15 unless base_price * qty >= 100 then 10 unless base_price * qty >= 200 then 0)
```

Decomposed pipeline (RIGHT):
```lemma
spec order_pricing

data base_price: number -> minimum 0
data qty:        number -> minimum 0

rule subtotal:             base_price * qty
rule discount_percentage:  0% unless qty >= 10 then 10%
rule discount_amount:      subtotal * discount_percentage
rule total_after_discount: subtotal - discount_amount
rule shipping_cost:        15 unless total_after_discount >= 100 then 10
rule final_total:          total_after_discount + shipping_cost
```

Hardcoded input (WRONG):
```lemma-skip
rule discount: 10 * 0.1
```

Input as data (RIGHT):
```lemma
spec bulk_discount

data qty: number -> minimum 0
rule discount: qty * 0.1
```

Wrong unless order (WRONG — VIP gets 20%, not 25%):
```lemma-skip
rule discount: 0%
  unless is_vip          then 25%
  unless qty >= 50  then 20%
```

Correct order (RIGHT):
```lemma
spec tiered_discount

data qty: number
data is_vip:   boolean

rule discount: 0%
  unless qty >= 50  then 20%
  unless is_vip          then 25%
```

Placeholder (WRONG):
```lemma-skip
data customer_name: "TODO"
```

Type-only input (RIGHT):
```lemma
spec customer_record

data customer_name: text
```

Error vs Veto: `5 and "text"` is planning Error (invalid Lemma). `unless age > 120 then veto "Invalid age"` is runtime Veto (valid spec, domain no-value). Do not confuse.

Unnecessary repo (WRONG):
```lemma-skip
repo default

spec pricing

data qty: number

rule discount: 0%
  unless qty >= 10 then 10%
```

Single-file spec (RIGHT):
```lemma
spec pricing

data qty: number

rule discount: 0%
  unless qty >= 10 then 10%
```


---

## Docs

- [Learn guide](https://github.com/lemma/lemma/blob/main/cli/documentation/learn/readme.md): specs, data, rules, unless, veto
- [Reference](https://github.com/lemma/lemma/blob/main/cli/documentation/reference/readme.md): operators, types, units, ranges, `uses`
- [Veto](https://github.com/lemma/lemma/blob/main/cli/documentation/learn/types_and_units.md#veto): propagation, `is veto`
- [Composing specs](https://github.com/lemma/lemma/blob/main/cli/documentation/learn/composing_specs.md): `uses`, temporal versions, pins
- [CLI](https://github.com/lemma/lemma/blob/main/cli/documentation/reference/cli.md): `run`, `show`, `format`
- [Registry](https://github.com/lemma/lemma/blob/main/cli/documentation/reference/registry.md): LemmaBase `@user/repo` imports
- [LemmaBase search](https://lemmabase.com/search?q=): registry search
- [Examples (docs)](https://github.com/lemma/lemma/tree/main/cli/documentation/examples): `.lemma` files
- [Examples (tests)](https://github.com/lemma/lemma/tree/main/cli/tests/integrations/examples): progressives

## Examples by pattern

- [Coffee order](https://github.com/lemma/lemma/blob/main/cli/documentation/examples/01_coffee_order.lemma): constraints, unless, veto enum
- [Library fees](https://github.com/lemma/lemma/blob/main/cli/documentation/examples/02_library_fees.lemma): unless chains, bool vs veto
- [Rules and unless](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/02_rules_and_unless.lemma): progressive unless
- [Spec references](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/03_spec_references.lemma): compound units, `uses lemma units`
- [Unit conversions](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/04_unit_conversions.lemma): duration, `as` conversion
- [Spec composition](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/11_spec_composition.lemma): hierarchy
- [Registry references](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/12_registry_references.lemma): registry import
- [Shipping policy](https://github.com/lemma/lemma/blob/main/cli/tests/integrations/examples/07_shipping_policy.lemma): decomposed rules

## Numeric precision (summary)

Exact rationals (ℚ) for `+`, `−`, `×`, `÷`, comparisons, conversions. `^`, `sqrt`, trig, `log` use ~28-significant-digit Decimal. `floor`/`ceil`/`round` on measures use Decimal.

**Boundary number contract:** WASM, HTTP JSON, Elixir NIF accept integers as numbers, reject float/decimal numbers. Pass decimals as strings. Example: `{ "quantity": 42, "rate": "0.075" }`. On Maven (JDK 21+), pass `BigDecimal` or decimal strings via `RunRequest.data(...)`; output is `BigDecimal`.

## Optional

- [LemmaBase](https://lemmabase.com): registry
- [Numeric precision](https://github.com/lemma/lemma/blob/main/cli/documentation/learn/precision.md): exact rational arithmetic, boundary contract
- [JavaScript / TypeScript (WASM)](https://github.com/lemma/lemma/blob/main/cli/documentation/tools/javascript.md): browser and Node
- [Maven (Java / Kotlin)](https://github.com/lemma/lemma/blob/main/cli/documentation/tools/maven.md): Central JNI package
- [README](https://github.com/lemma/lemma/blob/main/README.md): install, quick start