Safety & Best-Practice Rules
Every rule in CKL encodes a real cooking principle. These aren't arbitrary constraints—they are things that professional chefs know implicitly, written down so that everyone benefits.
Rules fall into four categories:
- Safety rules prevent mistakes that can make someone sick. A recipe that violates a safety rule will not compile. These are non-negotiable.
- Declaration rules keep the ingredient and equipment lists complete and consistent with the procedure. These are errors.
- Structural rules reject procedures that cannot be executed as written. These are errors.
- Best-practice rules flag technique issues that will produce poor results. A recipe will still compile, but you'll see a warning explaining what could go wrong.
Every diagnostic carries a code; the Rule Index lists all of them with their severity.
Safety Rules
These rules enforce food-safety science. They cannot be bypassed in normal mode.
Poultry must reach safe temperature (E101)
The principle: Poultry carries salmonella and campylobacter bacteria that are only destroyed at 74°C (165°F) or above. Cooking chicken to a lower temperature risks food-borne illness.
CKL enforces this automatically. If your recipe specifies a poultry target temperature below 74°C, it is rejected:
# This recipe is unsafe — CKL will stop you
ingredient "chicken breast" 500g
until internal_temp >= 60C # ✗ too low for poultryWrite it correctly:
ingredient "chicken breast" 500g
until internal_temp >= 74C # ✓ safe for poultryWhy 74°C? This is the instantaneous-kill temperature recommended by food safety authorities worldwide (USDA, FSA, EFSA). Lower temperatures can be safe with extended hold times (sous vide), but CKL enforces the conservative standard because a notation read by anyone must default to the safest guidance.
Poultry temperature is never verified (W101)
The principle: A recipe can be unsafe by omission. If poultry is declared but the recipe never checks internal temperature, nothing in the procedure establishes that the meat is cooked through — a timing-only instruction depends on thickness, starting temperature, and pan, none of which the recipe controls.
This is a warning, not an error: the recipe may genuinely verify doneness elsewhere (a sub-recipe, a stock that boils for hours). But a poultry recipe intended for a cook to follow should state the check explicitly.
ingredient "chicken thigh" 500g
pan "skillet" 30cm
heat pan to 180C
apply "chicken thigh" to pan
wait 12min # ⚠ W101 — time alone doesn't prove donenessAdd the verification:
wait 12min
until internal_temp >= 74C # ✓ doneness is establishedDeclaration Rules
These rules keep the "what you need" section complete and truthful. They are errors: a recipe whose ingredient or equipment list doesn't match its procedure cannot be followed.
Every ingredient must be declared (E102)
The principle: A recipe should account for everything that goes into a dish. Using an ingredient that was never listed in your "what you need" section means your ingredient list is incomplete—someone following the recipe won't have it ready.
This mirrors mise en place: before you start cooking, every ingredient is measured, prepared, and visible.
# Incomplete — "chicken" appears from nowhere
apply "chicken" to pan # ✗ not declaredDeclare it first:
ingredient "chicken" 500g # ✓ declared
apply "chicken" to panEvery piece of equipment must be declared (E103)
The principle: Just as you wouldn't start cooking without knowing which pans and tools you need, CKL requires that all equipment be declared before you reference it. This ensures the equipment list is complete and accurate.
# What pan? What size? A reader wouldn't know
heat pan to 180C # ✗ pan not declaredDeclare your equipment:
pan "skillet" 30cm # ✓ declared with size
heat pan to 180CEquipment must exist in the database (E104)
The principle: Equipment names are not free text — each one resolves to an entry in equipment/, which carries the purpose, materials, heat sources, and buying guidance shown on the generated site. An unrecognized name means the recipe links to nothing, so the name is rejected rather than silently dropped.
pan "nonstick frypan thing" 28cm # ✗ E104 — no such entryUse a known name or one of its aliases:
pan "non-stick pan" 28cm # ✓ resolves to equipment/non-stick-pan.jsonTo add genuinely new equipment, drop a JSON file into equipment/ — no compiler change is needed. The same check applies to the equipment named in a stage declaration.
No duplicate ingredient declarations (E105)
The principle: An ingredient appears once in the "what you need" list, with its full quantity. Declaring it twice means the shopping list is wrong and the true total is ambiguous — a reader cannot tell whether 30g and 20g means 50g or a correction.
ingredient "butter" 30g
ingredient "butter" 20g # ✗ E105 — declare 50g once insteadIf the ingredient is genuinely used at two points, declare the total once and split it across steps (see I601).
Every declared ingredient must be used (E106)
The principle: An ingredient listed but never used is the mirror image of E102 — the cook buys and measures something the procedure never calls for. In practice this is almost always a step that was deleted or never written.
ingredient "thyme" 3g # ✗ E106 — never applied to anythingEither use it in the procedure or remove the declaration.
Combine must reference declared stages (E107)
The principle: combine merges the results of named preparation stages. Referencing a stage that was never declared means there is nothing to merge — usually a typo in the stage name or a stage that was removed.
stage zabaione bowl "mixing bowl" 3l
stage cream_mix bowl "mixing bowl" 5l
combine zabaione whipped_cream in cream_mix # ✗ E107 — "whipped_cream" was never declaredDeclare every stage before combining it:
stage zabaione bowl "mixing bowl" 3l
stage whipped_cream bowl "mixing bowl" 2l
stage cream_mix bowl "mixing bowl" 5l
combine zabaione whipped_cream in cream_mix # ✓ both sources existBest-Practice Rules
These rules encode technique knowledge that experienced cooks take for granted. They produce warnings, not errors—your recipe will still compile, but the warning explains why the result may suffer.
Searing needs a hot pan (W001)
The principle: The Maillard reaction—the browning that creates deep flavor on the surface of meat—requires temperatures above ~150°C. Placing protein in a cold or insufficiently heated pan causes it to steam and stick rather than sear. The result is grey, tough meat without a crust.
# Warning: searing without heat
pan "skillet" 30cm
apply "steak" to pan # ⚠ pan isn't hotHeat the pan first:
pan "skillet" 30cm
heat pan to 200C # high heat for searing
apply "steak" to pan # ✓ Maillard reaction can occurDeglazing needs a hot pan (W002)
The principle: Deglazing is the technique of adding liquid to a hot pan to dissolve the fond (the browned bits stuck to the surface). If the pan is cold, the liquid won't sizzle, the fond won't release, and you lose the concentrated flavor that makes a pan sauce worth making.
# Warning: deglazing a cold pan
pan "skillet" 30cm
deglaze pan with "wine" # ⚠ pan isn't hotEnsure the pan is hot:
pan "skillet" 30cm
heat pan to 180C
deglaze pan with "wine" # ✓ fond will releaseShaping needs mixing first (W201)
The principle: Shaping a mixture — patties, dumplings, croquettes — relies on the structure that mixing develops. Forming something that was never mixed produces a mass that falls apart in the pan, because nothing binds the components together.
bowl "mixing bowl" 3l
apply "chickpeas" to bowl
apply "flour" to bowl
shape bowl into balls # ⚠ W201 — nothing has bound the mixtureMix before shaping:
mix bowl until combined
shape bowl into balls # ✓ mixture holds togetherPortioning more than you have (W202)
The principle: A batch cannot be divided into more than it contains. When a portion statement gives both a count and a weight, their product is checked against the total declared ingredient mass.
ingredient "flour" 320g
ingredient "butter" 226g
# ...
portion dough into balls weight 500g count 12 # ⚠ W202 — 6000g from 546gOnly the impossible direction is flagged. A real yield is always a little lower than the declared mass — evaporation, trimmings, what stays in the bowl — so a portioning that comes in under the total is never questioned.
Long holds belong in the refrigerator (W301)
The principle: rest and wait hold food at room temperature. Past two hours that is a food-safety decision, because 4–60 °C is the range where bacteria multiply — and more often than not, the recipe meant refrigeration and had no other word for it.
rest 240min # ⚠ W301 — four hours on the counter?If it is refrigeration, say so:
refrigerator "refrigerator"
chill dish in refrigerator for 240min to 4C # ✓This is a warning rather than an error because a long ambient ferment is a real technique — a dough proofing on the counter, a sourdough bulk rise. It should be a deliberate choice, not a default.
Structural Rules
Unlike the best-practice warnings above, these are errors — each marks a procedure that cannot be executed as written.
Consecutive wait instructions (E301)
The principle: Two consecutive wait or rest instructions are not allowed. Having them back-to-back is either a mistake — the durations should be added together — or a cooking action is missing between them. This is an error and will prevent compilation.
# Warning: consecutive waits
wait 10 min
wait 5 min # ⚠ consecutive wait — combine into oneCombine them:
wait 15 min # ✓ single waitOr insert an action between them if they serve different purposes:
wait 10 min
flip "chicken breast"
rest 5 min # ✓ action separates the waitsContradictory temperature (E302)
The principle: A recipe can name a temperature twice — once by setting the equipment, once on the technique that runs in it. When the two disagree, one of them is wrong, and a cook has no way to know which.
heat oven to 170C
bake dough in oven at 180C for 9min # ✗ E302 — 170 or 180?Say it once, or say the same thing twice:
heat oven to 180C
bake dough in oven at 180C for 9min # ✓ the preheat and the bake agreeThis is not a restriction on changing the temperature mid-recipe. Only heat and cool set the dial, and a technique's own at supersedes it — so restating the temperature on the next technique is exactly how a recipe turns the oven down:
heat oven to 180C
bake dough in oven at 180C for 9min
tap sheet 2times
bake dough in oven at 170C for 4min # ✓ this is the temperature changeCut requires knife (E401)
The principle: Any recipe that uses cut statements must declare a knife equipment. Cutting without a knife is physically impossible and would leave the recipe's equipment list incomplete. This is an error and will prevent compilation.
# Error: no knife declared
cut "onion" style dice # ✗ E401 — where's your knife?Fix by declaring a knife:
knife "chef's knife"
cut "onion" style dice # ✓ knife is declaredChill requires a refrigerator (E402)
The principle: chill exists because rest does not mean "put it in the fridge". A chill that never names where the chilling happens has thrown away the distinction it was added to preserve — and a refrigerator belongs on the equipment list like any other thing a cook needs to own.
# Error: chilled where?
chill dough for 720min # ✗ E402 — no location# Error: an oven is not a refrigerator
oven "standard oven"
chill dough in oven for 720min # ✗ E402 — wrong kind of equipmentDeclare the refrigerator and name it:
refrigerator "refrigerator"
chill dough in refrigerator for 720min to 4C # ✓Invalid reduction target (E501)
The principle: Every reduce statement must specify a valid culinary target describing the desired end state of the liquid. Vague or unknown targets are rejected because they make the recipe ambiguous — a cook reading "reduce 90min" has no idea whether the goal is a nearly-dry pan or a gently melded braise.
# Error: invalid target
reduce 5min until done # ✗ E501 — "done" is not a valid reduction targetValid targets are:
au_sec— reduced nearly dry (wine reductions, post-deglaze)nappe— coats the back of a spoon (sauces)thickened— noticeably thicker (tomato sauces)concentrated— intense, reduced volumemelded— flavors fully integrated (long braises)glossy— shiny, emulsified (butter sauces)
reduce 3min until au_sec # ✓ wine reduction
reduce 90min until melded # ✓ long braise for flavor integrationInvalid baking state or form (E502)
The principle: The same reasoning as E501, applied to the baking statements. cream, fold and portion end in a named state or form, and the named vocabularies are what keep "until fluffy" checkable while "until it looks right" stays out of the language.
cream "butter" "sugar" until golden # ✗ E502 — golden is not a creaming state
portion dough into blobs weight 80g # ✗ E502 — blobs is not a form| Statement | Vocabulary |
|---|---|
cream ... until | light, fluffy, pale, creamy, aerated, ribbon |
fold ... until | combined, uniform, streak_free, marbled, folded |
portion ... into | balls, patties, logs, rounds, discs, sheets, strips, dumplings, loaves, rolls, squares, wedges |
Appearance cues for a finished bake — golden edges, a soft centre — are not in these lists and are not meant to be. They belong in ## Notes; see the design note.
Measurement of the wrong kind (E503)
The principle: The lexer knows 60mm is a number with a unit. It does not know that a weight cannot be measured in millimetres. This rule is where that stops.
portion dough into balls weight 60mm # ✗ E503 — a weight is a mass
place dough on sheet spacing 50g # ✗ E503 — spacing is a length
portion dough into balls count 12.5 # ✗ E503 — you cannot make half a cookie
add "eggs" to dough in 1additions # ✗ E503 — a single addition is just 'add'| Clause | Accepts |
|---|---|
weight | g, kg |
size, spacing | cm, mm |
count, times | a whole number of at least 1 |
additions | a whole number of at least 2 |
Informational Rules
Informational diagnostics never fail a build. They point at places where a recipe is correct but could be more precise.
Split amounts across steps (I601)
The principle: An ingredient is declared with a single total, but used in several instructions. The recipe is valid — yet a cook reading step 4 has no way to know how much of the 600g goes in there.
ingredient "chicken breast" 600g # ℹ I601 — used in 4 instructionsWhere it matters, specify per-step amounts so they sum to the declared total. Where it genuinely doesn't — an ingredient added in one go across a couple of adjacent steps — the note can be left as is.
Rule Index
| Code | Severity | Rule |
|---|---|---|
| E101 | error | Poultry below 74°C minimum |
| E102 | error | Ingredient used but never declared |
| E103 | error | Equipment used but never declared |
| E104 | error | Equipment name not found in equipment/ |
| E105 | error | Duplicate ingredient declaration |
| E106 | error | Declared ingredient never used |
| E107 | error | combine references an undeclared stage |
| E301 | error | Consecutive wait / rest instructions |
| E302 | error | Technique temperature contradicts the dial |
| E401 | error | cut used without a declared knife |
| E402 | error | chill without a declared refrigerator |
| E501 | error | Invalid reduce target state |
| E502 | error | Invalid cream / fold state or portion form |
| E503 | error | Measurement clause of the wrong dimension |
| W001 | warning | Applying to unheated equipment (searing) |
| W002 | warning | Deglazing equipment that was never heated |
| W101 | warning | Poultry used without any temperature check |
| W201 | warning | Shaping a mixture that was never mixed |
| W202 | warning | Portions exceed the declared ingredient mass |
| W301 | warning | Ambient hold over two hours; consider chill |
| I601 | info | Ingredient total spread across several steps |
Strict Mode
By default, best-practice warnings let your recipe compile so you can iterate. When you want to enforce the highest standard—for example, before publishing a recipe—run in strict mode:
ckl lint recipes/ --strictIn strict mode, warnings become errors. Every sear must have sufficient heat. Every deglaze must follow heating. This is the standard used for recipes in the official knowledge base.
Extending the Rules
CKL's rule system is designed to grow as the knowledge base grows. New safety rules (safe temperatures for other proteins, allergen declarations) and new best-practice rules (resting times, emulsion stability) can be added by implementing the LintRule interface. See ARCHITECTURE.md for the technical details.