Skip to main content

Cookpit v3.2 — Governing Rules

The concise, numbered ruleset the AI Chef must obey when generating v3.2 JSON-LD cooking files. Each rule is independently checkable. The validator in validation.md references these rule numbers verbatim.

The AI Chef writing the file is a rebel chef detective: a confident working chef who reads the source recipe as a body of evidence and deduces the optimal schedule that satisfies it. The persona and stance are defined in bundle/v3.2/prompt.md and bundle/v3.2/lexicon.md §0. The rules below are the constraints that deduction must satisfy.


A. The three central principles

A1. Optimal. Every task time is the optimal moment an expert chef would commit to that action so the dish reaches its proper outcome. Times are factual culinary commitments deduced from the source's evidence, never random, cosmetic, or evenly spaced. The plan does not transcribe the source method line by line; it deduces the schedule the source implies.

A2. Closed. The plan is bounded by the resources declared in this file. Every reference must resolve to a declared resource of the matching type. The plan does not depend on tools, ingredients or sundries that are not declared. Equipment is declared at the heat-level abstraction the cook commits to, not at the heat-source provenance: hob is a hob, regardless of whether it is gas, electric, induction, charcoal, wood-fire or a volcano.

A3. Static. The plan does not adapt at runtime. The Chef app manages time and progress against the plan; the plan itself remains unchanged. Do not soften, pad, or stretch timings to make them more achievable, and do not insert filler tasks to fill quiet minutes between deduced commitments.


A0. The four lifecycle stages

A v3.2 cooking file passes through four stages, each with a single responsible actor and a single integrity question. The stages are strictly sequential; nothing in v3.2 mutates a file once a downstream stage has consumed it.

[source recipe] ── (1) generation ──▶ [U file] ── (2) validation ──▶ [verdict]

├── pass ── (3) attestation ──▶ [A file] ── (4) consumption ──▶ chef app runs
└── fail ──▶ report ──▶ user repairs ──▶ resubmit at stage 2

A0.1. Stage 1 — Generation. The AI Chef reads the source recipe and emits a v3.2 file. The file MUST carry a cookpit.quantitativeFingerprint that records the source's active-number sequence (section K), and a cookpit.attestation block whose status is unauthenticated (section R). The filename uses the U flag (section O). The file at this stage is the AI's claim that it has faithfully transcribed the source. It has not yet been confirmed by anyone.

A0.2. Stage 2 — Validation. The validator runs the candidate file through every hard criterion in validation.md, including source-faithfulness checks against the source recipe (V-FINGERPRINT-B, V-SOURCE-COVERAGE, V-SOURCE-TEMPS, V-METHOD-ORDER). The validator never mutates the file. The output is a verdict: pass or fail. A failed file goes back to the user (or the AI repair loop) and may be resubmitted. A passed file proceeds to stage 3.

A0.3. Stage 3 — Attestation. The validator stamps the passed file. It canonicalises the file body with cookpit.attestation.signature cleared, computes the SHA-256 file fingerprint, signs the canonical payload with its private key, replaces the cookpit.attestation block with the authenticated form (section R), and renames the file to use the A flag (section O). Only the canonical validator may issue an A file.

A0.4. Stage 4 — Consumption. A downstream consumer (the Chef app, a recipe library, an integrity audit) loads the file and verifies it. The consumer trusts only the cryptographic binding: parse, schema-conform, re-canonicalise, recompute the file fingerprint, verify the signature against the published public key, optionally check a revocation list, optionally enforce a minimum validatorVersion. The consumer does NOT re-run source-faithfulness checks (it does not have the source recipe); those were settled at stage 2 and certified at stage 3.

A0.5. Single actor per stage. The AI is the actor at stage 1; the validator at stages 2 and 3; the consumer at stage 4. The validator is the only actor that touches both the source-faithfulness check (stage 2) and the file-content certification (stage 3) — it is the handoff point between the two integrity questions.

A0.6. Two distinct fingerprints, two distinct stages.

FingerprintHashes whatStageComputed byVerified by
Source fingerprint (cookpit.quantitativeFingerprint)The source recipe's active-number sequence (section K)1 → 2AI at stage 1; validator re-checks at stage 2Validator (V-FINGERPRINT-B)
File fingerprint (cookpit.attestation.fileFingerprint)The canonicalised file body with signature cleared (section R)3 → 4Validator at stage 3Consumer (V-FILE-FINGERPRINT, V-SIGNATURE)

The source fingerprint catches AI errors of fidelity to the source. The file fingerprint catches post-attestation tampering with the cooking file. Each detects a class of failure the other cannot. They are not redundant.

A0.7. Filename flag is decorative. The filename's A/U flag (section O) is a human-readable cue. It is NOT a security signal. Consumers MUST verify trust through cookpit.attestation.status, the file fingerprint, and the signature — never through the filename alone. Filename / internal-status disagreement is a hard validation failure (V-ATTESTATION-CONSISTENCY).


B. File identity

B1. The top-level @type includes both Recipe and cookpit:CookingFile.

B2. The cookpit.version is exactly 3.2.0.

B3. The $schema URL identifies the v3.2 cooking file schema.

B4. The cookpit.id is type-prefixed with f (file) and matches the global ID pattern (see G1).

B5. cookpit.courses is a non-empty array drawn from starter, main, dessert, with no duplicates and at most three entries.

B6. cookpit.difficulty is exactly one of easy, medium, hard, expert.


C. Source timing and phase durations

C1. cookpit.sourceTiming preserves the source recipe's timing fields: prepTime, cookTime, totalTime when the source uses ISO 8601 durations, or prepTimeText / cookTimeText when the source uses prose or ranges. Source facts are recorded as the source states them.

C2. Phase durations. Each declared phase carries its own nominalDuration (HH:MM:SS) in the phase block (cookpit.prepCook .nominalDuration, cookpit.preCook.nominalDuration, cookpit.liveCook.nominalDuration). The liveCook duration is derived from the source's cook time the source assigns to the final-assembly window — never the source's total time. The prepCook and preCook durations are derived from the source's explicit timed prep / pre-cook windows. There is no top-level nominalCookDuration in v3.2; references throughout the rules to "the cook duration" mean the duration of the phase the rule speaks about.

C3. Range resolution. When the source's cook time or a method-body duration is a range, the canonical resolution is determined by the following priority order. The same rules apply within any phase (prepCook, preCook, liveCook):

C3.1. Method-body specificity wins over header-range estimates. When the source header gives a wide range (e.g. Cook: 10 to 30 mins) and the method body specifies a precise duration (Bake for 15 minutes), the method-body value drives the relevant phase's nominalDuration and the timingBasis of any task using it is sourceExactDuration. The header range is preserved verbatim in cookpit.sourceTiming.cookTimeText.

C3.2. Single range, no competing total: range minimum. A method-body range like simmer for 20-25 minutes with no contradicting source total resolves to range minimum. The task uses timingBasis.basis: "sourceRangeMinimum".

C3.3. Two parallel ranges + a competing total: range maximum. When two long parallel processes share a window WITHIN A PHASE and the source states a total time for that window, taking the minimum of each range may undershoot the source's total. In this case, BOTH parallel ranges resolve to range MAXIMUM to align with the source's stated total. The pork-fillet-braised-cheeks-and-pork-belly file is the canonical case: in the preCook phase, source 6-8 hours cheek braise + 7-8 hours confit run concurrently, with source-stated preCook total 8 hours. Range minima would undershoot; range maxima align. Both tasks use sourceRangeMinimum basis with the source phrase preserved in timingBasis.source; the rationale for the range-maximum departure is documented in a prerequisites.notes[] item.

C3.4. Source explicitly invites the upper bound: range target. When the source phrases the range as a quality-driven choice (bake until golden, 10-12 minutes; the longer the bake the deeper the colour), the task uses timingBasis.basis: "sourceRangeTarget" and resolves to the target value the canonical profile names — typically the upper bound for outcome quality.

C3.5. Open lower bound (Over X hours, At least X minutes): compositional sum. When the source's cook time is an open lower bound rather than a closed range, the chef-detective derives the relevant phase's nominalDuration by summing method-body explicit durations plus canonicalProcessEstimate fills for any source step without a stated duration. The chosen value MUST satisfy the open lower bound. The pork-fillet-braised-cheeks-and-pork-belly file is again the canonical case: the source's Cook: Over 2 hours is the composition of an 8-hour preCook plus a ~1 h 45 min liveCook.

In all cases, the chosen value is recorded in the phase block's nominalDuration and the source range or open bound is preserved verbatim in cookpit.sourceTiming.cookTimeText.

C4. cookpit.orchestration.timingBasis is cookTime. prepHandling is preStartChecklist. runtimeOverruns is appOwned.

C5. Residual end-buffer is permitted within any phase. When the source's stated duration for a phase exceeds the deduced sequence duration of that phase's tasks, the phase MAY carry a residual buffer between the last task and the phase's time-up alarm. Filling the buffer with invented tasks (per A3 "do not pad or stretch timings") is forbidden. Residual buffers are honest representations of sources that overstate duration relative to actual sequence duration; the active corpus shows a 30-second buffer in boeuf bourguignon as the canonical example.


D. Lane model

D1. The cookpit.laneModel block is the fixed primary/secondary/tertiary course lanes block as published in the v3.2 spec, used verbatim.

D2. A0 is the global alarm lane and is used only for global alarms, klaxons, warnings and whole-session milestones (start, remaining-time warnings, time-up, major transitions).

D3. Course-scoped lanes carry only that course's prompts: S1/S2/S3 carry only starter prompts, M1/M2/M3 carry only main prompts, D1/D2/D3 carry only dessert prompts.

D4. Lanes are intra-minute publication slots. A course's three lanes (S1/S2/S3, M1/M2/M3, D1/D2/D3) provide three slots within every minute, spaced five seconds apart. Use them in either of two valid ways: (a) parallel workstreams — the primary lane carries the main workstream of the course, secondary and tertiary lanes carry simultaneous workstreams that run alongside it; or (b) tight intra-minute sequences — when the source crowds a moment with multiple sub-actions in a single minute, the secondary and tertiary lanes carry those sub-actions in their source order five seconds apart. Either use is conformant. Filler use to spread tasks across minutes is not.

D5. The seconds component of every task time matches its lane: A0=:00, S1=:15, S2=:20, S3=:25, M1=:30, M2=:35, M3=:40, D1=:45, D2=:50, D3=:55.


E. Required global alarms (per phase)

The alarm rules below apply independently to each declared phase (prepCook, preCook, liveCook). Each phase has its own clock that runs from 00:00:00 to that phase's nominalDuration; alarms are mechanical features of that local clock. The Chef app starts a fresh A0 timer for each phase the file declares.

E1. Each phase's tasks[] contains exactly one start-cooking alarm on A0 at 00:00:00.

E2. Each phase's tasks[] contains exactly one time-up alarm on A0 at the value of that phase's nominalDuration. For liveCook this alarm is the serve cue; for prepCook and preCook it is the phase-complete cue that hands off to the next phase.

E3. When a phase's nominalDuration is at least 10 minutes, that phase's tasks[] contains a 10-minutes-remaining alarm on A0 at nominalDuration − 00:10:00.

E4. A 5-minutes-remaining alarm on A0 at nominalDuration − 00:05:00 is included within a phase when it is useful for that phase; if included it must be on A0 and at the correct minute.

E5. Standard global alarms have kind: "alarm" and do not require a timingBasis; they are derived mechanically from the phase's nominalDuration.


F. Prep, prerequisites and the three phases

F1. File-level prerequisites are the entry checklist. Recipe prep that the user confirms once before any phase begins lives in cookpit.prerequisites. This is the file's only checklist — there are no per-phase prerequisite blocks. Static prep that satisfies its deadline trivially (chopping onions ahead, bruising garlic, grating cheese, beating eggs, dissolving stock) is the default home for prep. Items that need to be done in advance carry a leadTime ISO 8601 duration (P1D for overnight marination, PT3H for cooling stock).

F2. Promote prep to a live timed task on a course lane only when it must run live. Live placement is required when one of the following holds: the action must happen inside a phase's cook window for quality, freshness, kitchen-flow or temperature reasons (deglazing, finishing herbs into a sauce, mounting butter at the end, slicing meat off the bone before serving, tempering chocolate, melting butter into a hot pan); the source explicitly schedules it inside a phase with a "while X cooks" or "meanwhile" cue; or its outcome state cannot be held without quality loss until the live moment of use. Otherwise, prefer prerequisites.

F3. Phase selection is by source evidence, not authoring preference. A timed phase block is declared only when the source establishes a discrete timed window:

PhaseDeclare when the source describes
prepCookA timed active prep window with stated duration — pressing meat under weights, salt-curing, marinating with active monitoring, mise that genuinely takes long enough to warrant a runtime timer.
preCookCooking of mainstay components ahead of final assembly — long braises whose product becomes part of the final dish, bake-then-cool meringue bases, poach-and-shred salmon for a pâté.
liveCookThe final-assembly cook ending in serving. Always declared.

A file with no source evidence for prepCook or preCook declares only liveCook. The vast majority of recipes are liveCook-only.

F4. Phase activation. allPrerequisitesConfirmed enables BOTH cookpit.prepCook (if declared) and cookpit.preCook (if declared) at the same moment — the Chef app starts each declared phase's A0 timer when the user confirms file-level prerequisites. prepCook and preCook may run concurrently or sequentially; the file does not encode that runtime choice. cookpit.liveCook becomes available only when the LAST of {prepCook, preCook} fires its A0 time-up alarm — liveCook never overlaps prepCook or preCook. The file does not encode runtime checkbox state.

F5. Live prep stays live. The presence of a cookpit.prepCook block does NOT relocate prep out of cookpit.liveCook. Prep that F2 places inside the live cook window — deglazing, finishing herbs into a sauce, mounting butter at the end, slicing meat off the bone before plating, tempering chocolate, melting butter into a hot pan, "while X cooks" / "meanwhile" actions, prep whose outcome state cannot be held without quality loss — remains in liveCook. prepCook is the home only for source-stated active timed prep windows that happen BEFORE the cook day (presses, salt-cures, active-monitored marinades). F2 is the sole authority on whether prep is live or pre-Start; the three-phase model does not weaken it.


G. Type-prefixed deterministic IDs

G1. Every entity ID matches the global pattern ^[a-z][0-9a-f]{10}$: a single lowercase type letter followed by 10 lowercase hex characters.

G2. Type prefixes:

EntityPrefix
cooking file / liveCook (cookpit.id)f
ingredienti
equipmente
utensilu
sundrys
prerequisite item (any group)q
processp
taskt
hotspoth
prepCook phase (cookpit.prepCook.id)y
preCook phase (cookpit.preCook.id)z

G3. IDs are deterministic, not random. The required derivation is:

<typePrefix> + first 10 hex of SHA-256("v3.2|" + entityType + "|" + canonicalContent + "|" + canonicalPosition)

The canonical generation profile (cookpit-ai-canonical-v3.2) defines the exact entityType, canonicalContent and canonicalPosition strings per entity type, with self-test vectors. See bundle/v3.2/canonical-id-derivation.md for the full profile.

G4. IDs are unique within the file.

G5. Cross-references match by exact string. A reference whose prefix does not match the referenced entity's type is a hard validation failure.


H. Resource closure

H1. Every ingredientRefs id exists in cookpit.ingredients[].id and starts with i.

H2. Every equipmentRefs id exists in cookpit.equipment[].id and starts with e.

H3. Every utensilRefs id exists in cookpit.utensils[].id and starts with u.

H4. Every sundryRefs id exists in cookpit.sundries[].id and starts with s.

H5. Every processRefs id on a task exists in the processes[] array of the SAME phase that owns the task and starts with p. Cross-phase process references are forbidden — each phase has its own roster.

H6. Process startTask and endTask ids exist in the tasks[] array of the SAME phase that owns the process and start with t.

H7. Hotspot taskRefs ids exist in the tasks[] array of any declared phase and start with t. Hotspots are file-level metadata and may target tasks in any phase.

H8. Task action text does not depend on equipment, utensils, ingredients or sundries that are absent from the declared resource lists.

H9. Every declared resource is referenced by at least one task, process or prerequisite. (Soft warning only: a recipe may legitimately list a "to serve" item that is not in the timed plan.)


I. Tasks

I1. tasks is a non-empty array. Each task has id, time, kind, action. Tasks whose kind is not alarm additionally have timingBasis.

I2. kind is one of alarm, alert, update. alarm is reserved for the A0 lane. alert and update are placed on course lanes.

I3. time matches ^([0-9]{2}):([0-9]{2}):([0-9]{2})\.(A0|S[1-3]|M[1-3]|D[1-3])$.

I4. The seconds component of time matches the trailing lane label per D5.

I5. The hours/minutes component of every task time is at most the nominalDuration of the phase that owns the task. No task is scheduled past its phase's time-up.

I6. Tasks are ordered by time. When two tasks share the same HH:MM:SS component, ordering is time then lane then id.

I7. action text is culinary and direct. UI verbs (tap, swipe, confirm, press, done) are forbidden in action.

I8. For every non-alarm task, timingBasis records the kind of evidence the chef-detective used to choose the time. basis is one of:

basisuse it when
sourceExactDurationthe source states a single fixed duration ("for 10 minutes").
sourceRangeMinimumthe source states a range; you took the canonical minimum ("25–30 minutes" → 25).
sourceRangeTargetthe source states a range; the canonical generation profile selected the target rather than the minimum.
sourceCookTimeEndpointthe time is derived mechanically from the phase's nominalDuration (start, time-up, remaining-time alarm).
sourceOrderthe source places this action at a specific point in the narrative order with no explicit duration; the time records that order.
sourceMeanwhilethe source explicitly schedules this action inside another action's window ("while the spaghetti is cooking…").
sourceOutcomeCuethe time is set by an outcome cue rather than a clock ("until deep golden", "until juices run clear").
sourceImpliedDeadlinethe source gives no time for the prep itself but states a downstream consumer ("add the chopped onion"); the prep is placed at deadline − prep duration. Record the consumer task in offsetFrom and the prep duration in offset (negative ISO 8601, e.g. -PT2M).
canonicalProcessEstimatethe source is silent on duration and there is no downstream deadline; the time reflects competent professional practice for that action and outcome, with a one-line professional rationale in source.

The source field is the specific source line whose evidence justifies the chosen time — not the source line that happens to mention the action.

I9. No task time is random, cosmetic or used as filler. Every non-alarm task time has a defensible derivation in timingBasis.


J. Processes and outcome cues

J1. Each process has id (p…), label, course, startTask, endTask, duration and completion. Processes live INSIDE a phase block: cookpit.<phase>.processes[]. startTask and endTask reference task ids that exist in the SAME phase's tasks[].

J2. The interval between startTask.time and endTask.time matches the process's duration.target to within rounding allowed by the lane model. Both endpoints are in the same phase clock, so the interval is straightforward subtraction.

J3. completion uses one of the spec's allowed types (timed, sensory, temperature, compound). Completion cues describe outcome, not runtime state.

J4. Within a phase, processes are listed in the order their startTask.time occurs.


K. Source faithfulness and the strict quantitative fingerprint

K1. Every numeric fact in the source recipe (ingredient quantities, durations, temperatures, gas marks, ranges) appears either in cookpit.ingredients or in the tasks / processes plan, with the same value(s) the source states.

K2. No quantity, duration, temperature or range is silently changed, rounded or omitted. Range minima may be selected per C3 but the original range is preserved in source-text fields.

K3. cookpit.quantitativeFingerprint is present, of type: "strict", with basis: "ingredients-and-method-active-numbers" and normalization: "cookpit-active-number-sequence-v3.2.0".

K4. quantitativeFingerprint.sequence is the dash-separated sequence of active numbers extracted from the source ingredient lines and method, in source order, normalised per the canonical generation profile (cookpit-active-number-sequence-v3.2.0), matching ^[0-9]+(-[0-9]+)*$. See bundle/v3.2/canonical-fingerprint-normalisation.md for the full tokenisation rules and worked examples.

K5. quantitativeFingerprint.hash.algorithm is sha256. quantitativeFingerprint.hash.value is the lowercase 64-hex SHA-256 of the sequence string. The hash is the file's identifying key for its source numeric skeleton.


L. Forbidden runtime fields

L1. The file does not author any runtime state. Forbidden anywhere in the file: urgency, priority, progress, completionState, checkboxState, actualStartTime, actualFinishTime, overdue, overdueState, focusTask, bannerColor, timerColor, overrunState, queuePosition, scrollPosition, and any field whose name implies runtime mutation or visual UI state.

L2. The file does not contain dynamically extended times or any timing field that varies based on user pace.


M. Generation metadata

M1. cookpit.generation.profile identifies the canonical generation profile (default: cookpit-ai-canonical-v3.2).

M2. cookpit.generation.idPolicy is deterministic-type-prefixed-10-hex.

M3. cookpit.generation.timingPolicy is source-derived-deterministic-optimal.

M4. cookpit.generation.resourcePolicy is closed-world-declared-resources.

M5. cookpit.generation.randomTimingAllowed is false.


N. Harvested fields

N1. Optional ingredients. An ingredient may carry optional: true for "to serve" / decorative items. Optional ingredients are not required to be referenced by a task or process.

N2. Equipment power and notes. Equipment items may carry an optional power (electric, gas, induction, none) and a free-text notes field. The chef-AI may use power to make timing decisions where the source is silent on heat-up time.

N3. Per-task sound override. Tasks may carry an optional sound chosen from bell, klaxon, chime, tick. When omitted, the lane's default sound applies. Sound is not a substitute for severity.

N4. Prerequisite leadTime. Prerequisite items may carry an optional leadTime ISO 8601 duration for make-ahead steps (e.g. P1D for overnight marination). The live timer still starts at 00:00:00; leadTime is informational metadata for the Chef app's pre-cooking reminders.

N5. Prerequisite resource refs. Prerequisite items may link to the resources they concern via ingredientRefs (each i…), equipmentRefs (each e…), utensilRefs (each u…) and sundryRefs (each s…). All targets must exist in the corresponding cookpit.<group>[].id (closure rule applies). The validator's V-REFS-COVERAGE soft check recognises each as a legitimate consumer of its target resource — so a knife declared in cookpit.utensils[] and referenced from a prereq item's utensilRefs is no longer flagged as "unreferenced".

N6. Source-stated alternatives. Ingredients may carry an alternative sub-object preserving a substitute the source recipe explicitly offers as a primary-plus-fallback (e.g. "vanilla pod, or vanilla extract"). The plan is still authored against the primary; alternative is metadata only.

N7. Source-stated equivalents (choices[]). When the source recipe offers a set of EQUALLY VALID same-role options ("cod, haddock or pollock"; "pearl onions OR 24 baby onions"; "chicken or pork stock"; "deep fryer or deep saucepan"), the ingredient (or equipment) carries a choices[] array. Each choice records its own quantity+unit pair. The chef picks one. Distinct from alternative, which encodes a single fallback. Use choices[] when none of the listed options is "primary" — they are equivalents.

N8. Ingredient splits (splits[]). When a single declared ingredient is partitioned across multiple tasks at distinct fractions (e.g. carbonara's "most of the cheese for the egg-mix, a small handful for the topping"; boeuf's "half the butter for cooking, half for finishing"), the parent ingredient carries a splits[] array. Each split has its own i… id and a fraction (0 < f ≤ 1). Tasks reference the split's id like any other ingredient ref; the closure rule treats each split as a valid usage of the parent. The Chef app uses split metadata to show portion-aware pre-cook prep and run-time tracking.


O. Filename and media-type methodology

O1. The filename pattern is <slug>.<schema-version>.<dialect>.<status>.jsonld, where <status> is the single-character lifecycle flag A (authenticated — the file has been stamped by the canonical validator) or U (unauthenticated — the file has not been stamped). The flag is a human-readable cue; it is NOT a security signal (see A0.7 and section R).

O1.1. AI Chef output (stage 1) MUST use <status> = U and MUST embed cookpit.attestation.status: "unauthenticated".

O1.2. Validator output (stage 3) flips the filename's <status> to A when (and only when) every hard criterion in validation.md passes and the validator embeds an authenticated cookpit.attestation block per section R. The filename flag and the internal cookpit.attestation.status MUST agree at every stage (V-ATTESTATION-CONSISTENCY).

O2. The slug is derived deterministically from the recipe name by Unicode NFKD normalization, dropping combining marks, lowercasing, replacing any non-[a-z0-9] character with the chosen separator (either - or _), collapsing repeats, trimming, and truncating to 80 characters at a separator boundary. The chosen separator is project-wide and applied consistently; mixing - and _ within a single filename is forbidden. The existing in-repo convention is _; new projects may elect -. See canonical-patterns.md §6.

O3. <schema-version> is the major-minor schema version: v3.2.

O4. <dialect> is cpt (Cookpit).

O5. The file extension is always .jsonld (W3C-recognised).

O6. When served over HTTP, the file uses Content-Type: application/ld+json; profile="https://cookpit.org/spec/v3.2".

O7. Examples.

  • spaghetti_carbonara.v3.2.cpt.U.jsonld — AI Chef output, before validation.
  • spaghetti_carbonara.v3.2.cpt.A.jsonld — same file after stage-3 attestation.

O8. Backward-compatibility note. Files predating this rule revision that lack the <status> segment (e.g. the in-repo corpus's spaghetti_carbonara.v3.2.cpt.jsonld) are treated as U for stage-4 consumer purposes. New tooling SHOULD rename such files to add the explicit U flag at the next opportunity. The flag is required for any file that has been put through the validator; an A file MUST carry the flag.


P. Lexicon and chef voice

The chef-language and terminology guide is published alongside this rules list as bundle/v3.2/lexicon.md. The lexicon defines the persona, voice, verb taxonomy, heat language, sensory vocabulary, forbidden terms, allowed informalisms, source-faithful exceptions and regional defaults.

P1. Persona. Every active cooking instruction is written in the rebel chef voice defined in lexicon.md §0: confident, easy-going, concise, plain English by default, specialist terms only when they earn their place. The persona is uniform across every v3.2 file generated from this bundle.

P2. Scope. The lexicon applies to every field that carries an active cooking instruction (tasks[].action, tasks[].completion.cue, processes[].label, processes[].completion.cue, and prerequisite items whose text describes a cooking action). The lexicon's tone applies more lightly to skills, hotspots, notes and equipment notes; it does not apply to declarative naming of ingredients, equipment, utensils or sundries.

P3. Imperative form. Active cooking instructions are imperative, present-tense, second-person pronouns omitted. Sentence fragments are allowed (Off heat. Tent it. Rest ten.); one-word commands are not the goal.

P4. Verb selection. Cooking verbs are chosen per lexicon.md §3 with their kitchen-precise meaning. Specialist terms (brunoise, chiffonade, mantecare, tadka) are used only when the source recipe uses them or plain English loses precision.

P5. Heat language. Heat words follow the calibrated meanings in lexicon.md §4. Source temperatures, gas marks and °F are preserved in timingBasis.source and rendered as °C in canonical action text.

P6. Time language. Active task time is exact (5 minutes, 20 minutes). "A little while", "a few minutes", "a moment" are forbidden in active instructions. Adverbs of manner (quickly, gently, patiently) are allowed but never as substitutes for a number.

P7. Sensory completion cues. Every completion.cue carries at least one sensory token from lexicon.md §6 (or a clear synonym). completion.type: timed is permitted without a sensory cue; all other completion types must have sensory specificity.

P8. Forbidden language. Active cooking instructions never contain hedgers (a little, as desired, you'll want to, make sure to), recipe-blog warmth (lovely, perfect, delicious, amazing, wonderfully, beautifully), filler (now, go ahead and, simply), or vague outcomes without a sensory companion (until done, until cooked through).

P9. Allowed informalisms. Working-chef phrasings listed in lexicon.md §7.1 are encouraged where they fit (low and slow, off heat, back off, tip in, pull from heat, rest ten, no colour, don't crowd the pan, scatter, tent it).

P10. Source-faithful exceptions. When the source recipe uses culinarily precise specialist terms (per lexicon.md §8), preserve them. When the source already reads in canonical chef voice, do not paraphrase.

P11. Process-label grammar. A processes[].label is a short present-continuous noun phrase (Reducing the sauce, Resting the meat), not an imperative.

P12. Regional default. UK English by default; source-region usage preserved when the source is regionally specific.


Q. Phase blocks (prepCook, preCook, liveCook)

The three-phase model is v3.2's mechanism for honestly representing recipes that contain timed prep windows, pre-cooked mainstay components, or both, before final assembly. The rules below constrain how phases compose. Per-phase content rules (alarms, tasks, processes, lanes) are inherited from sections E, I, J, D respectively.

Q1. liveCook is required; prepCook and preCook are optional. cookpit.liveCook is always present. cookpit.prepCook and cookpit.preCook are declared only when the source establishes a discrete timed window for them (per F3). A liveCook-only file is the canonical default; the active corpus shows varying phase counts (carbonara, goulash, boeuf and roast chicken with cider and sage: liveCook only; pork-fillet-braised-cheeks-and-pork-belly: prepCook + preCook + liveCook).

Q2. Phase identity. cookpit.prepCook.id is type-prefixed y…, cookpit.preCook.id is type-prefixed z…. cookpit.liveCook has no id field — its identity is the file's cookpit.id (f…). Phase ids are deterministic per canonical-id-derivation.md.

Q3. Phase shape. Each declared phase carries label, nominalDuration, tasks[], optional processes[] and optional completion. The phase's tasks[] and processes[] are self-contained — every task and every process in the phase lives within that phase's local clock (00:00:00 to nominalDuration).

Q4. Phase ordering. Two distinct ordering relations govern the three phases at runtime:

  • prepCookpreCook (independent). When both are declared, they may run concurrently or sequentially; the chef chooses at runtime. The file does not encode that choice. Each carries its own A0 timer; either may finish first without unblocking liveCook.
  • liveCookprepCook and liveCookpreCook (strictly downstream). liveCook is unblocked only when the LAST of the declared upstream phases has fired its A0 time-up alarm. liveCook never overlaps prepCook or preCook.

The JSON document order in cookpit is fixed (prepCook, preCook, liveCook) regardless of runtime choice — this is a human-readability convention, not a runtime sequencing claim. The Chef app's UI offers the chef the option to start prepCook and preCook in parallel or in series.

Q5. No cross-phase task or process references. A task's processRefs[] may name only processes declared in the SAME phase. A process's startTask/endTask may name only tasks declared in the SAME phase. Cross-phase continuity is encoded structurally by phase ordering (Q4), not by reference.

Q6. File-level prerequisites cover the whole file. cookpit.prerequisites is confirmed once before the FIRST declared phase begins. There is no per-phase prerequisites block. Items needed only for a later phase still live in cookpit.prerequisites and may carry a leadTime if make-ahead is required.

Q7. File-level resources cover the whole file. cookpit.ingredients[], cookpit.equipment[], cookpit.utensils[] and cookpit.sundries[] are the file-wide closed-world resource lists. Every phase's tasks and processes resolve their refs against these lists. The closure rules in section H apply across phases.

Q8. Phase completion cue. The optional cookpit.<phase>.completion records the sensory cue that signals the phase is done. liveCook's completion cue is the dish-on-plate cue. preCook's is the cooked-component cue ("cheeks tender to a knife tip; meringue dry on top"). prepCook's is the prep-done cue ("belly compressed to half its starting depth").

Q9. Phase fingerprint scope. cookpit.quantitativeFingerprint remains file-scoped: it covers all numbers from all phases plus ingredients, in the source order they appear in the source recipe. Per-phase fingerprints are not declared.


R. Attestation and lifecycle integrity

Section R defines the cookpit.attestation block and the cryptographic binding that allows a stage-4 consumer to verify a file was stamped by the canonical validator and has not been altered since. The block is authored by the AI in its unauthenticated form at stage 1 and replaced by the validator in its authenticated form at stage 3.

R1. Block presence. Every v3.2 file carries cookpit.attestation, regardless of stage. AI Chef output (stage 1) MUST emit it with status: "unauthenticated". The validator's stamping operation (stage 3) replaces it with the authenticated form.

R2. Unauthenticated form (stage 1, stage 2 input). The block has a single required field:

  • status: "unauthenticated".

It MAY carry advisory selfReported data (rulesSelfChecked, validatorRun, etc.). It MUST NOT carry a signature, fileFingerprint or keyId. A stage-1 file claiming any of these fields is malformed and the validator rejects it (V-ATTESTATION-SHAPE).

R3. Authenticated form (stage 3 output). The block has the following required fields:

  • status: "authenticated".
  • issuer — the canonical validator's issuer URL (e.g. https://cookpit.spec/v3.2/validate).
  • validatorVersion — exact validator version string.
  • issuedAt — UTC timestamp of stamping. The format is RFC 3339 in the form YYYY-MM-DDTHH:MM:SSZ — UTC ("Z" suffix), second precision (no fractional seconds), no timezone offset other than Z. The regex is ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$. The format is pinned because the signed canonical bytes include issuedAt; two validators producing different formats for the same logical instant would produce non-interoperable signatures.
  • canonicalization — the canonicalisation profile name (default: RFC8785).
  • keyId — identifier of the public key used to sign.
  • fileFingerprint — lowercase 64-hex SHA-256 of the canonicalised file body computed per R5.
  • signature — base64-encoded cryptographic signature over the canonical signed payload defined in R6.

It MAY carry an audit sub-object holding the validator's report summary (hardFailures, softWarnings, infos) and any non-trust metadata. The signature covers the audit sub-object so that audit data is tamper-evident even though it is not trust-bearing.

R4. AI Chef contract. The AI MUST emit the unauthenticated form per R2. The AI MUST NOT emit the authenticated form, MUST NOT invent a signature, fileFingerprint, issuer, keyId, validatorVersion or issuedAt, and MUST NOT claim status: "authenticated" under any circumstance. The AI is not a trust authority.

R5. Canonicalisation and file fingerprint. The file fingerprint is computed as follows:

  • Take the file's JSON body.
  • Set both cookpit.attestation.signature AND cookpit.attestation.fileFingerprint to the empty string "". All other cookpit.attestation fields (status, issuer, validatorVersion, issuedAt, canonicalization, keyId, audit) remain in place. (Setting the two fields to empty rather than removing them keeps the signed payload's shape identical to the eventual authenticated payload, so consumers do not need to mutate field structure during verification. Both fields must be cleared together because the fingerprint is the hash of the bytes the signature is computed over, and a populated fileFingerprint would otherwise change those bytes recursively.)
  • Serialise the result with the canonicalisation profile named in canonicalization (default RFC 8785 JCS): lexicographic key ordering, normalised numeric forms, no insignificant whitespace, fixed string escaping. The validator's portable fallback profile, used when RFC 8785 tooling is unavailable, is cookpit-canonical-v3.2.0 — NFC-normalised strings, lexicographic key ordering at every level, ASCII-only escaping, tight separators.
  • Compute the lowercase 64-hex SHA-256 digest of the canonical bytes.

That digest is cookpit.attestation.fileFingerprint.

R6. Signature payload and verification. The signature is computed over the same canonical bytes the fingerprint is computed from in R5 — i.e. over the entire canonicalised file with both signature and fileFingerprint cleared. The fingerprint is therefore the SHA-256 of the exact bytes the signature is over; consumers do a single canonicalisation pass and use the result for both checks. The signature still binds the file end-to-end: tampering with any field elsewhere — issuer, validatorVersion, issuedAt, canonicalization, keyId, audit, the cooking plan body — alters the canonical bytes and invalidates both the fingerprint match and the signature.

A stage-4 consumer verifies a file by:

  1. Reading cookpit.attestation, requiring status: "authenticated".
  2. Confirming issuer matches the consumer's pinned canonical issuer.
  3. Confirming keyId resolves to a public key the consumer trusts.
  4. Re-canonicalising the file with signature cleared and recomputing the SHA-256 digest. The digest MUST equal fileFingerprint.
  5. Verifying the signature over the canonical bytes using the trusted public key.
  6. Optionally checking the published revocation list and the consumer's accepted validatorVersion policy.

Any step's failure is a hard rejection.

R7. One-way transition. The unauthenticated → authenticated transition is one-way and irreversible by editing. A consumer that finds an authenticated block whose signature does not verify MUST treat the file as untrusted; it does NOT downgrade to U semantics silently. Re-validation requires the user to strip the attestation block back to the unauthenticated form and resubmit at stage 2.

R8. Validator refusal modes. The canonical validator MUST refuse to issue an authenticated block when:

  • Any hard criterion in validation.md fails.
  • The submitted file already carries an authenticated attestation block (it must be stripped back to unauthenticated and resubmitted).
  • The submitted file claims status: "authenticated" without a valid signature, fingerprint and key id (malformed).

In all refusal cases the validator returns the unmodified file plus the report.

R9. Forbidden runtime fields excluded. cookpit.attestation is plan metadata, not runtime state. Section L's runtime-state heuristics explicitly exempt the attestation block; trust metadata does not mutate at runtime.

R10. Filename flag agreement. The filename's <status> segment (section O) MUST agree with cookpit.attestation.status. A file named …cpt.A.jsonld whose internal status is unauthenticated, or named …cpt.U.jsonld whose internal status is authenticated, is malformed and rejected by the validator (V-ATTESTATION-CONSISTENCY). The filename remains decorative; the cryptographic binding remains the load-bearing trust signal.

R11. Create-and-consume decoupling. The attestation block is created by the validator at stage 3 and consumed by downstream chef apps (and other authenticated-file readers) at stage 4. The two operations are decoupled in time: a v3.2 file may sit indefinitely between stage 3 and stage 4. The file's authenticity claims remain valid as long as the consumer can verify the signature against a trusted public key. There is no separate timing or expiry semantics in the attestation block; revocation, when it exists, is a runtime concern of the consumer's trust- anchor management and is out of scope for this section.


Conformance

A v3.2 file is conformant if and only if it satisfies every rule above and validates against the v3.2 JSON Schema. Any failure of any rule is a hard validation failure. The validator in validation.md enumerates how each rule is checked.

A v3.2 file is authenticated if, in addition, it has been stamped by the canonical validator at stage 3 per section R, carries the A filename flag per section O, and verifies under the consumer-side verification flow R6 against the published public key.