# Cookpit v3.2 — extended reference

You are reading https://cookpit.org/v3.2/reference.md. This is the companion to https://cookpit.org/v3.2/ai. Fetch it when you need:

- The field-by-field meaning of any `cookpit.*` value (`glossary.md`).
- The full validation criteria the validator will apply to your output (`validation.md`).
- The README for the bundle, including the four-stage lifecycle table (`README.md`).
- The architectural overview of how the v3.2 schema fits together (`architecture.md`).

You do not need this document to produce a valid file — `ai.md` is sufficient. Use this when a field is ambiguous, you want to anticipate validator failures, or you need to understand how the pieces relate.

---

<!-- file: README.md -->

# README

# Cookpit v3.2 — Publishable Generation Bundle

This directory holds the **publishable artefacts** for v3.2 Cookpit cooking
files. A user (or an online tool acting on their behalf) gives these to an
LLM of their choice along with a source recipe, and gets back a JSON-LD
cooking file that the v3.2 validator can accept or reject.

The bundle is intentionally **portable**: any LLM may attempt the task. The
validator is the gate — it tempers the model's enthusiasm by judging the
output against the v3.2 JSON Schema, the rules in `rules.md` and the
criteria in `validation.md`. Models that follow instructions well and
support structured-output JSON will pass more often, but conformance is
decided by the validator, not by the model's reputation.

## The persona — rebel chef detective

The AI Chef writing the file is a **rebel chef detective**. They read the
source recipe as a body of evidence — every ingredient line, every method
sentence, every stated duration, every "add", "pour", "fold", "season" is
a clue — and they deduce the optimal schedule the recipe implies. They do
not transcribe the source method line by line. They write the deduced
schedule in the rebel-chef voice defined in `lexicon.md §0`.

The detective stance has two consequences for every v3.2 file:

- Prep that the source mentions without a time gets placed by working
  *backwards* from the moment the prep is needed (`Add the chopped onion`
  → onion in pan at moment T → chop deadline T → place prep at T − chop
  duration). The new `sourceImpliedDeadline` `timingBasis.basis` records
  the deduction.
- Prep that satisfies its deadline trivially (chopping ahead, grating
  cheese ahead, beating eggs ahead) is promoted to
  `cookpit.prerequisites`. Prep is kept live only when quality, freshness,
  kitchen-flow or temperature reasons force it inside the cook window.

## Files

| File | Role |
| --- | --- |
| `prompt.md` | The optimised AI Chef system prompt. |
| `rules.md` | The concise governing rules, numbered for cross-reference. |
| `lexicon.md` | The chef language and terminology guide — persona, vocabulary, heat language, sensory cues, forbidden terms, allowed informalisms. |
| `validation.md` | The validation criteria, mapped 1:1 to the rules. |
| `canonical-id-derivation.md` | The canonical generation profile `cookpit-ai-canonical-v3.2`: per-entity-type rules for deterministic id derivation, with self-test vectors. Referenced by `rules.md` G3 and the validator's `V-IDS-DETERMINISTIC` check. |
| `canonical-fingerprint-normalisation.md` | The canonical normalisation `cookpit-active-number-sequence-v3.2.0`: tokenisation rules that turn a source recipe's text into the strict quantitative fingerprint sequence. Referenced by `rules.md` K3-K5 and the validator's `V-FINGERPRINT-B` check. |
| `source-content-handling.md` | Categorisation rules for the five kinds of non-method source content (sponsored / chrome / culinary explanation / structurally-actionable / source typo). Referenced by `prompt.md` and the corpus's prereq-note conventions. |
| `canonical-units.md` | The unit vocabulary used in `cookpit.ingredients[].unit` and adjacent fields. Seven classes (metric mass / volume, UK measure, imperial mass / volume, count and structural, semantic). |
| `canonical-patterns.md` | Six concurrency patterns + lane-model dual reading + leadTime scale + process-label phase qualifiers + detective-inserted check tasks + filename slug separator. Walk-confirmed patterns that the chef-detective and downstream consumers can rely on as canonical references. |
| `glossary.md` | The definitive field-by-field reference for the v3.2 schema. Treats v3.2 as a clean-slate format: every value defined by `schema/cookpit-cooking-file-v3.2.json` is documented (name, JSON type, purpose, constraints, references) in 15 sections. Indexes the v3.2 world rather than re-explaining it; cross-references the canonical documents above. |
| `README.md` | This file. |

## How they relate

- `prompt.md` is given to the LLM as the **system message**.
- `rules.md` is given to the LLM alongside the source recipe so it can
  self-check before emitting.
- `lexicon.md` is given to the LLM alongside the rules. It defines the
  **voice** — a rebel chef, easy-going and concise — that every active
  cooking instruction must adopt. The lexicon governs `tasks[].action`,
  `tasks[].completion.cue`, `processes[].label`,
  `processes[].completion.cue` and prerequisite items that carry a
  cooking action. It does not apply to declarative naming of ingredients,
  equipment, utensils or sundries.
- `validation.md` is the **validator's specification**. It is not given to
  the LLM; it is the operator-side / Chef-app-side acceptance test that the
  generated file must pass to be considered conformant v3.2.

The v3.2 JSON Schema (`schema/cookpit-cooking-file-v3.2.json`) sits beside
this bundle as the executable structural contract.

The executable validator (`scripts/validate_cookpit_v3.2.py`) is the
runnable embodiment of `validation.md` and the two canonical profiles
above. Run it against any v3.2 candidate file to get a per-criterion
pass/fail/warn/info report.

## The three central principles of v3.2

1. **Optimal.** Every task time is the optimal moment an expert chef would
   commit to the action so the dish reaches its outcome — deduced from
   the source's evidence, not transcribed from its narrative order.
2. **Closed.** The plan is bounded by the resources declared in the file.
   Equipment is declared at heat-level abstraction: `hob` is a hob,
   regardless of whether the heat comes from gas, induction, electric,
   charcoal, wood-fire or a volcano.
3. **Static.** The plan does not adapt at runtime; the Chef app manages
   time and progress against the fixed plan. No padding, no filler, no
   stretched timings.

These principles are enumerated in `prompt.md` (for the AI), restated in
section A of `rules.md` (for both the AI and the validator), and enforced
through the criteria in `validation.md` (for the validator).

## The four lifecycle stages

Every v3.2 cooking file passes through four stages, each with a single
responsible actor and a single integrity question (`rules.md` A0):

| # | Stage | Actor | Output | Filename flag | Integrity question |
| --- | --- | --- | --- | --- | --- |
| 1 | Generation | AI Chef | unauthenticated file with `cookpit.attestation.status: "unauthenticated"` and `cookpit.quantitativeFingerprint` (source) | `U` | "Has the AI faithfully captured the source's numbers?" |
| 2 | Validation | Canonical validator | pass / fail verdict | (input remains `U`) | "Does the file conform to v3.2 and faithfully reflect its source?" |
| 3 | Attestation | Canonical validator | authenticated file with `fileFingerprint`, `signature`, `keyId`, etc. | `A` | "Has the validator approved this exact file body?" |
| 4 | Consumption | Chef app / library / audit tool | trusted run, or rejection | `A` (verified) | "Has anything changed since the validator stamped it?" |

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 between the two integrity questions, and the single trust
authority that can issue an `A` file. AI Chefs never produce `A` files;
consumers never accept files on filename flag alone.

Two distinct fingerprints support these stages:

- **Source fingerprint** at `cookpit.quantitativeFingerprint`
  (`rules.md` K, `canonical-fingerprint-normalisation.md`) — AI populates,
  validator confirms at stage 2.
- **File fingerprint** at `cookpit.attestation.fileFingerprint`
  (`rules.md` R, A0.6) — validator computes at stage 3, consumer verifies
  at stage 4.

The two fingerprints answer different questions and are not redundant.
The filename's `A`/`U` flag is decorative; the cryptographic binding
inside the attestation block is the load-bearing trust signal
(`rules.md` A0.7, R10).


---

<!-- file: architecture.md -->

# Architecture

# Cookpit v3.2 Schema — How It Works

**Subject:** A component-by-component architectural walkthrough of the cookpit v3.2 system, explaining how each file in the schema, bundle, scripts, tests, and docs collaborates across the four-stage lifecycle (generate → validate → attest → consume).
**Audience:** Engineers, schema designers, validator implementers, Chef-app builders, and reviewers who need to understand *what each file does, why it is necessary, what it depends on, and what it produces*.
**Scope:** Architecture only. The recipe corpus is deliberately ignored.

---

## 1. The Big Picture

Cookpit v3.2 is not a single file format — it is a **four-stage pipeline** with strict separation of concerns. A source recipe (typically a PDF or text web page) is converted into a deterministic, source-faithful, audit-grade JSON-LD plan, then validated, then cryptographically attested, then consumed by a runtime cooking application.

```
   ┌─────────────────────────────────────────────────────────────────────┐
   │                     COOKPIT v3.2 LIFECYCLE                          │
   ├─────────────────────────────────────────────────────────────────────┤
   │                                                                     │
   │  Source recipe (PDF / text)                                         │
   │          │                                                          │
   │          ▼                                                          │
   │  ┌──────────────┐    Stage 1: Generation                            │
   │  │   AI Chef    │    Inputs: prompt + rules + lexicon + schema      │
   │  │     LLM      │    + canonical-id + canonical-fingerprint         │
   │  └──────────────┘    Output: <slug>.v3.2.cpt.U.jsonld               │
   │          │                                                          │
   │          ▼                                                          │
   │  ┌──────────────┐    Stage 2: Validation                            │
   │  │  Validator   │    Inputs: U file + validation.md spec            │
   │  │ (script .py) │    + schema.json + canonical profiles + source    │
   │  └──────────────┘    Output: pass/fail verdict + per-criterion log  │
   │          │                                                          │
   │     hard-pass                                                       │
   │          ▼                                                          │
   │  ┌──────────────┐    Stage 3: Attestation                           │
   │  │  Validator   │    Inputs: U file + signing key                   │
   │  │  (signing)   │    Output: <slug>.v3.2.cpt.A.jsonld               │
   │  └──────────────┘    (file fingerprint + Ed25519 signature)         │
   │          │                                                          │
   │          ▼                                                          │
   │  ┌──────────────┐    Stage 4: Consumption                           │
   │  │  Chef app    │    Inputs: A file + trusted public key            │
   │  │ (consumer)   │    Output: live cooking experience                │
   │  └──────────────┘                                                   │
   │                                                                     │
   └─────────────────────────────────────────────────────────────────────┘
```

The files in the repository fall into five layers that map onto this pipeline:

| Layer | Files | Role |
| --- | --- | --- |
| **Schema (contract)** | `schema/cookpit-cooking-file-v3.2.json`, `docs/schema-v3.2.md` | Defines the shape every file must satisfy. |
| **Authoring bundle (stage 1 inputs)** | `bundle/v3.2/{README, prompt, rules, lexicon, glossary}.md` | Tells the AI Chef how to generate a file. |
| **Canonical profiles (deterministic algorithms)** | `bundle/v3.2/canonical-{id-derivation, fingerprint-normalisation, units, patterns}.md`, `source-content-handling.md` | Pin the algorithms that must produce identical output across implementations. |
| **Validation (stage 2/3 enforcement)** | `bundle/v3.2/validation.md`, `scripts/validate_cookpit_v3.2.py`, `scripts/lib/source_tokeniser.py`, `tests/test_source_tokeniser.py` | Specifies and executes the gate; signs files that pass. |
| **Consumer (stage 4 spec)** | `docs/chef-app-spec.md` | Describes how a Chef app should turn a signed file into a live cooking session. |

The remainder of this document walks through each layer in turn, explains what every file does and why it is necessary, then traces a single recipe end-to-end through the lifecycle.

---

## 2. The Schema Layer — The Contract

### `schema/cookpit-cooking-file-v3.2.json`

**Purpose.** A JSON Schema (Draft 2020-12) document that defines the *executable structural contract* for every v3.2 cooking file. Every file in the corpus must validate against it.

**What it contains.** Definitions for the top-level `Recipe` shape, the `cookpit` block extension, the three `phaseBlock` types (`prepCook`, `preCook`, `liveCook`), the `task` and `process` shapes, the lane-second `if/then` rules, the `timingBasis` enum, the `quantitativeFingerprint` shape, the `generation` metadata block, and the `attestation` block (`oneOf` between unauthenticated and authenticated forms).

**Who reads it.** The AI Chef (as response-shape constraint during generation), the validator (as the V-SCHEMA hard criterion), and any third-party tooling that wants generic JSON-Schema validation.

**Why it is necessary.** Without it, every consumer would have to re-derive the file shape from prose. With it, any standard JSON-Schema validator can perform a first-pass shape check; structural mismatches are caught before any cookpit-specific logic runs.

**What it does *not* do.** It enforces only the structural contract (types, enums, required fields, `if/then` rules). Cross-reference closure (every `ingredientRef` must resolve), determinism of IDs, source-faithfulness of fingerprints, and lifecycle invariants are *not* expressible in JSON Schema and therefore live in the validator and bundle rules instead.

### `docs/schema-v3.2.md`

**Purpose.** A long-form, human-readable companion to the JSON Schema that explains *why* the schema looks the way it does. It introduces the three central principles (Optimal, Closed, Static), the four lifecycle stages, the three-phase model, the lane model, the orchestration semantics, and walks each field with design rationale.

**Who reads it.** New developers, documentarians, reviewers, and anyone who wants to understand the *narrative* behind the schema rather than just its grammar.

**Why it is necessary.** A JSON Schema document is a grammar; it does not explain choices. `schema-v3.2.md` is where decisions like "why three phases?", "why fixed lanes per course?", "why two fingerprints?" are reasoned about so that downstream consumers and future schema authors can extend the format coherently.

---

## 3. The Authoring Bundle — Inputs to the AI Chef

The bundle is the set of documents the AI Chef reads during stage 1. It is portable: any LLM that can follow markdown system messages and emit JSON can be paired with this bundle to produce v3.2 output.

### `bundle/v3.2/README.md`

**Purpose.** A directory manifest and entry point. It introduces the persona, lists every bundle file with its role, summarises the four-stage lifecycle, and articulates the three central principles (Optimal, Closed, Static).

**Who reads it.** Human authors and the LLM at the start of any generation task. It is the orientation document.

**Why it is necessary.** Without it, the bundle is a folder of nine markdown files with no apparent reading order. The README's role table and "how they relate" section give a coherent first read.

### `bundle/v3.2/prompt.md`

**Purpose.** The system message used to drive the AI Chef. It defines the rebel-chef-detective persona, the AI's role in the four-stage lifecycle (specifically, that it produces **stage-1 unauthenticated output only**), the Phase 0 decomposition decision tree (which phase a piece of work belongs to), the Phase 1 resource-selection method, the Phase 2 deductive working order, the `timingBasis.basis` enum with worked examples, the style-and-tone guidance, the explicit "what never appears in the file" negative space, and the output-format pinning to a single JSON object.

**Who reads it.** The LLM, as the system message at generation time.

**Works with.** `lexicon.md` (paired voice input), `rules.md` (referenced for normative rules), `schema/cookpit-cooking-file-v3.2.json` (response shape), `canonical-id-derivation.md` (Phase 2 ID generation), `canonical-fingerprint-normalisation.md` (Phase 2 fingerprint computation).

**Why it is necessary.** It is the only document that tells the LLM *how to think* about a recipe: where the work is, when each step happens, which voice to use, what to never invent. Without it, the LLM is given a schema and no method.

### `bundle/v3.2/rules.md`

**Purpose.** Nineteen normatively-numbered rule sections (A through R) that govern every aspect of a v3.2 file: the lifecycle (§A0), the three principles (§A1–A3), file identity (§B), timing and phases (§C), the lane model (§D), global alarms (§E), prep and phase composition (§F), deterministic IDs (§G), resource closure (§H), tasks (§I), processes (§J), the source fingerprint (§K), forbidden runtime fields (§L), generation metadata (§M), optional fields (§N), filename (§O), lexicon and persona (§P), phase blocks (§Q), and attestation (§R).

**Who reads it.** The LLM (as a self-check before emitting), the validator (every hard validation criterion maps to a rule by number), human reviewers and QA.

**Works with.** Almost every other file in the bundle. It is the spine that the rest hang off. `validation.md` references rule numbers; `prompt.md` references rule sections by name; the canonical profile documents are pointed to from §G3 (IDs) and §K3–K5 (fingerprint).

**Why it is necessary.** It is the *contract*. The schema enforces shape; rules.md enforces semantics. Without it, "what counts as a valid cookpit file" beyond mere JSON-Schema compliance is undefined.

### `bundle/v3.2/lexicon.md`

**Purpose.** The chef-language and voice guide. It defines the rebel-chef persona by negative space (not Jamie, not Mary Berry, not brigade, not blogger), specifies the imperative-fragment register, the heat-level mapping (gas marks, °C, stovetop levels), the time-language constraints (exact only — no "for a few minutes"), the sensory-cue vocabulary across visual / aural / tactile / olfactory modalities, the forbidden tokens (hedgers, warmth, filler), the explicitly-allowed informalisms ("low and slow", "off heat", "tent it"), and a 50-row translation table that anchors the rebel voice between brigade and recipe-blog.

**Who reads it.** The LLM, as paired input alongside `prompt.md` and `rules.md`.

**Works with.** `prompt.md` (the persona is referenced from there), `rules.md` §P (which makes the lexicon normative), and downstream `validation.md` (V-LEX-* soft criteria).

**Why it is necessary.** Without the lexicon, action text drifts toward generic recipe-blog warmth or sterile robot-prose. The lexicon is what makes a v3.2 file *sound* like a chef rather than like an ML model.

### `bundle/v3.2/glossary.md`

**Purpose.** A field-by-field reference for every field in the v3.2 schema. It is structured as a comprehensive index that points each field to its canonical authority — `rules.md` for normative behaviour, `canonical-*.md` for algorithms, `schema-v3.2.md` for design rationale.

**Who reads it.** Developers building tooling, reviewers checking field semantics, schema authors evolving v3.3.

**Why it is necessary.** The information about each field exists scattered across the schema, the rules, and the canonical profiles. The glossary collapses that into one alphabetised index so that a reader who needs to know "what does `cookpit.attestation.audit` mean?" has a single place to look first.

---

## 4. Canonical Profiles — Deterministic Algorithms

The canonical-* documents are the bundle's *executable specifications*. They define algorithms that must produce byte-identical output across any conformant implementation.

### `bundle/v3.2/canonical-id-derivation.md`

**Purpose.** Specifies the canonical generation profile `cookpit-ai-canonical-v3.2`: every entity ID in a v3.2 file is derived as `<typePrefix> + first-10-hex(SHA-256("v3.2|<entityType>|<canonicalContent>|<canonicalPosition>"))`. The document defines per-entity-type rules for what `canonicalContent` and `canonicalPosition` mean (file, ingredient, equipment, utensil, sundry, prereq, hotspot, process, task, prepCook, preCook, liveCook), the type-prefix table (`f / i / e / u / s / q / p / t / h`), the Unicode normalisation rules (NFC, diacritics preserved), and a self-test vector set so independent implementations can prove conformance.

**Works with.** `rules.md` §G3 (which makes the profile normative), the LLM at stage 1, the validator at stage 2 (V-IDS-DETERMINISTIC re-derives every ID and compares).

**Why it is necessary.** Determinism is what makes the closed-world resource graph trustworthy. If two implementations produced different IDs for the same recipe, cross-references would break, deduplication would fail, caching would invalidate. The profile turns ID generation into a verifiable computation rather than a convention.

### `bundle/v3.2/canonical-fingerprint-normalisation.md`

**Purpose.** Specifies the canonical profile `cookpit-active-number-sequence-v3.2.0` for computing the *source-faithfulness fingerprint*. It defines a six-stage pipeline:

- **Stage A — Extraction:** PDF/text → UTF-8 with ligature expansion, curly-quote folding, soft-hyphen stripping, whitespace normalisation, diacritic preservation.
- **Stage B — Segmentation:** identify header / ingredients / method / tips blocks.
- **Stage C — Filtering:** remove sponsored content, paywall chrome, step markers, header noise (using `source-content-handling.md` categories).
- **Stage D — Tokenisation:** apply eight numeric patterns + Unicode-fraction map + cooking-context heuristics to extract every active number.
- **Stage E — Rendering:** dash-join tokens into a deterministic sequence string.
- **Stage F — Hashing:** SHA-256 the sequence; store the digest.

The document includes a worked example (spaghetti carbonara, 17 tokens) and a nine-source-PDF tokeniser self-test corpus.

**Works with.** `rules.md` §K3–K5, `source-content-handling.md` (Stage C uses its categories), `scripts/lib/source_tokeniser.py` (the executable embodiment), `tests/test_source_tokeniser.py` (the conformance gate), and the validator's V-FINGERPRINT-A (shape) and V-FINGERPRINT-B (re-extraction) criteria.

**Why it is necessary.** The fingerprint is the format's defence against numerical hallucination. If the LLM invents a temperature, a quantity, or a duration absent from the source, the fingerprint computed from the source will not match the fingerprint stored in the file, and V-FINGERPRINT-B will fail. This document is what makes that check independently reproducible.

### `bundle/v3.2/canonical-units.md`

**Purpose.** Defines the seven canonical unit classes — metric mass (g, kg, mg), metric volume (ml, cl, l), UK measure (tbsp, tsp, dsp), imperial mass (oz, lb), imperial volume (fl oz, pint, cup), count/structural (count, clove, sprig, bunch, slice, head, pod, stick, leaf, pinch, dash, knob, handful, cm, inch), and semantic (`toTaste`, `toServe`, `asNeeded`, `forGreasing`). Each entry has a canonical token; mixing across classes is permitted but must use canonical tokens only.

**Works with.** `glossary.md` §8 (referenced for ingredient-unit definition), `chef-app-spec.md` (shopping lists, scaling), the LLM during ingredient generation.

**Why it is necessary.** Without a canonical vocabulary, "tbsp" and "tablespoon" and "Tbsp" and "TBSP" would all appear, and tooling could not reliably parse units for shopping lists, dietary calculations, or scaling arithmetic. The document collapses the chaos to a single allowed spelling per concept.

### `bundle/v3.2/canonical-patterns.md`

**Purpose.** A walk-confirmed catalogue of structural patterns that recur across the corpus — six concurrency patterns (parallel workstreams, intra-minute clusters, cross-lane processes, shared-boundary chains, serial-vessel chains, fully-concurrent passive), the lane-model dual reading (parallel vs tight-sequence), the `leadTime` vocabulary (`P1D` overnight, `PT8H` multi-hour, `PT15M` oven preheat), process-label phase qualifiers, detective-inserted check tasks for 6-hour-plus cooks, filename slug conventions, and phase-composition patterns (liveCook-only, preCook+liveCook, prepCook+liveCook, full three-phase).

**Works with.** `rules.md` §J, §D, §N4, §Q, §O; `prompt.md` (Phase 0 decomposition); `chef-app-spec.md` (which displays these patterns at runtime).

**Why it is necessary.** Patterns are the *implementation playbook*. Without them, the AI must re-derive the same structural decisions for every recipe and the Chef app has no canonical reference for interpreting concurrency. The patterns are a shared vocabulary that keeps the corpus internally consistent.

### `bundle/v3.2/source-content-handling.md`

**Purpose.** Categorises source content into five kinds — sponsored content, paywall chrome, culinary explanation, structurally-actionable tip, source typo — and specifies how each is handled in *both* the file body (`recipeInstructions[]`, `tasks[].action`, prereq notes) *and* the source fingerprint (Stage C filtering).

**Works with.** `canonical-fingerprint-normalisation.md` (Stage C filtering uses these categories); `prompt.md` and `rules.md` (which point to it for filtering decisions); `scripts/lib/source_tokeniser.py` (which implements the filtering).

**Why it is necessary.** Real-world source recipes carry significant non-recipe content — adverts, paywall banners, chef commentary, step markers, occasional typos. Without an explicit categorisation, two implementations would filter inconsistently and their fingerprints would diverge. This document is the rosetta stone between messy source content and the canonical fingerprint sequence.

---

## 5. The Validation Layer — Stage 2 and Stage 3 Enforcement

### `bundle/v3.2/validation.md`

**Purpose.** The validator's *executable specification*. It enumerates ~46 criteria — roughly 26 hard (must pass for the file to advance to Stage 3) and ~20 soft (advisory, surfaced in the report). Each criterion has an ID (`V-PARSE`, `V-SCHEMA`, `V-LIFECYCLE-AI-EMITS-U`, `V-IDS-DETERMINISTIC`, `V-REFS-CLOSED`, `V-FINGERPRINT-A`, `V-FINGERPRINT-B`, `V-FILE-FINGERPRINT`, `V-SIGNATURE`, etc.), a severity (hard/soft), a check description, and a back-reference to the rule it enforces.

**Who reads it.** The validator implementer, the LLM (knows what will be checked), reviewers triaging validator output.

**Works with.** `rules.md` (every criterion references a rule), `schema/cookpit-cooking-file-v3.2.json` (V-SCHEMA), `canonical-id-derivation.md` (V-IDS-DETERMINISTIC), `canonical-fingerprint-normalisation.md` (V-FINGERPRINT-B), `scripts/validate_cookpit_v3.2.py` (the implementation).

**Why it is necessary.** Without a normative validation specification, the validator and the AI would drift apart. validation.md keeps them in lockstep: the LLM knows what to satisfy; the validator knows what to check; the rules document says *why*.

### `scripts/validate_cookpit_v3.2.py`

**Purpose.** The executable validator. It implements every criterion from `validation.md` as a Python function. It also performs the Stage-3 cryptographic attestation: on hard-pass, it canonicalises the file body with the `signature` and `fileFingerprint` fields cleared, computes the SHA-256 file fingerprint, signs the canonical bytes with an Ed25519 private key, embeds the authenticated attestation block, renames the file from `.U.jsonld` to `.A.jsonld`, and writes it.

**CLI surface.** `--validate-only` (dry-run, no signing), `--verify` (Stage-4 consumer-style verification of an existing `.A.jsonld`), `--source <pdf|text>` (input for V-FINGERPRINT-B), `--key`, `--gen-key`, `--in-place`.

**Works with.** Every canonical profile and every rule in the bundle (transitively); `scripts/lib/source_tokeniser.py` (V-FINGERPRINT-B); the file system (input U file, output A file).

**Why it is necessary.** It is the single point at which the format's correctness is *enforced*. Without it, all rules in the bundle are advisory.

### `scripts/lib/source_tokeniser.py`

**Purpose.** The executable embodiment of `canonical-fingerprint-normalisation.md`. It runs the full six-stage pipeline (extraction → segmentation → filtering → tokenisation → rendering → hashing) and produces both the rendered active-number sequence and its SHA-256 digest.

**Who calls it.** `scripts/validate_cookpit_v3.2.py` from V-FINGERPRINT-B; the test suite; any tool that needs to compute a source fingerprint.

**Why it is necessary.** Without it, V-FINGERPRINT-B is unimplementable and source-faithfulness is unverifiable. The tokeniser is what turns the prose specification into a deterministic computation.

### `tests/test_source_tokeniser.py`

**Purpose.** Unit tests organised stage-by-stage (TestStageA ligatures and quotes, TestStageB segmentation, TestStageC filtering, TestStageD tokenisation patterns and Unicode fractions, TestStageE rendering, TestStageF hashing) plus a conformance gate that runs the §9 worked example (carbonara) and the §11 tokeniser self-test corpus of source PDFs, checking SHA-256 equality byte-for-byte.

**Why it is necessary.** Without the tests, regressions in the tokeniser would silently change every fingerprint in the corpus. The carbonara conformance gate is the canary that catches this.

---

## 6. The Consumer Layer — Stage 4 Specification

### `docs/chef-app-spec.md`

**Purpose.** Defines the runtime experience that a Chef app must deliver when consuming a `.A.jsonld` file. It specifies screen layout (top third identity and timer; bottom two-thirds prerequisites then live banners), action banners with checkboxes, pending tasks as stacked black banners, the colour-state machine (black pending, blue outstanding, navy complete, orange overdue, red urgent), the runtime time-management contract (the *app* owns the timer, urgency, progress, and pending state — the file is static), and the explicit separation of concerns between the cookpit JSON-LD file and the dynamic app state.

**Who reads it.** Chef-app developers, UX designers, and v3.2 schema authors who want to know which runtime behaviours the schema must support and which it deliberately delegates.

**Why it is necessary.** A static file format alone does not deliver a real-time cooking experience; an app does. `chef-app-spec.md` is the contract between the format (what the file guarantees) and the runtime (what the app must add). Without it, "what should an app do with a v3.2 file" is undefined and every implementation diverges.

---

## 7. End-to-End Walkthrough: One Recipe Through Four Stages

A worked trace of how the pieces collaborate on a single recipe.

### Stage 1 — Generation

1. Operator hands the LLM the bundle (`prompt.md` as system message, plus `rules.md`, `lexicon.md`, the canonical profiles, the schema, and the source recipe).
2. LLM reads `prompt.md` for persona and method.
3. **Phase 0 (decomposition):** classifies each piece of work into prepCook / preCook / liveCook per the `prompt.md` decision tree, cross-checked against `canonical-patterns.md` §8 phase-composition patterns.
4. **Phase 1 (resources):** declares ingredients, equipment, utensils, sundries, skills, hotspots — using `canonical-units.md` for unit selection and `rules.md` §H for closure.
5. **Phase 2 (deduction):** assigns tasks to lanes per `canonical-patterns.md` §1–2; chooses a `timingBasis.basis` per `prompt.md` worked examples and `rules.md` §I8; generates IDs per `canonical-id-derivation.md`; computes the source fingerprint per `canonical-fingerprint-normalisation.md` and `source-content-handling.md`.
6. LLM emits a single JSON object with `cookpit.attestation.status: "unauthenticated"` and writes it as `<slug>.v3.2.cpt.U.jsonld`.

### Stage 2 — Validation

1. Operator runs `scripts/validate_cookpit_v3.2.py <slug>.v3.2.cpt.U.jsonld --source <slug>.pdf`.
2. The validator parses the file (V-PARSE), validates against the schema (V-SCHEMA), inspects the attestation block (V-LIFECYCLE-AI-EMITS-U / V-ATTESTATION-CONSISTENCY), then runs all hard criteria — re-deriving every ID via `canonical-id-derivation.md` (V-IDS-DETERMINISTIC), enforcing closed-world references (V-REFS-CLOSED), checking source coverage and source temperatures (V-SOURCE-COVERAGE / V-SOURCE-TEMPS), and re-extracting the source fingerprint via `scripts/lib/source_tokeniser.py` (V-FINGERPRINT-B).
3. Soft criteria (V-LEX-FORBIDDEN, V-LEX-IMPERATIVE, V-DURATION-B, etc.) are surfaced as warnings.
4. The validator returns a verdict and a per-criterion report. On any hard failure, the file does not advance.

### Stage 3 — Attestation

1. On hard-pass, the validator clears `cookpit.attestation.signature` and `cookpit.attestation.fileFingerprint`.
2. Canonicalises the JSON body (deterministic key ordering, no insignificant whitespace).
3. Computes `SHA-256(canonical bytes)` → `fileFingerprint` (V-FILE-FINGERPRINT).
4. Signs the same canonical bytes with the validator's Ed25519 private key (V-SIGNATURE).
5. Replaces the attestation block with the authenticated form (`status: "authenticated"`, `issuer`, `validatorVersion`, `issuedAt`, `keyId`, `fileFingerprint`, `signature`, `audit`).
6. Verifies the signature against its own public key as a self-check.
7. Renames the file to `<slug>.v3.2.cpt.A.jsonld` and writes it.

### Stage 4 — Consumption

1. Chef app loads `<slug>.v3.2.cpt.A.jsonld`.
2. Parses the attestation block; refuses to proceed unless `status === "authenticated"`.
3. Confirms `issuer` against a pinned trusted validator URL.
4. Resolves `keyId` to a trusted public key.
5. Re-canonicalises the body with `signature` cleared, recomputes SHA-256, verifies it matches `fileFingerprint`.
6. Verifies the signature over the canonical bytes against the public key.
7. (Optionally) checks revocation list and minimum `validatorVersion`.
8. On success, loads the cooking plan into the runtime per `chef-app-spec.md` — confirms prerequisites, starts the live timer, posts banners, manages pending state, colour-codes overruns. The file itself is never modified.

---

## 8. Cross-Reference Graph (condensed)

```
                schema/cookpit-cooking-file-v3.2.json
                              ▲
                              │ structural shape
                              │
prompt.md ─────► rules.md ◄───┴──── validation.md ────► validate_cookpit_v3.2.py
   │              │                       │                       │
   │              │                       ▼                       │
   ├────► lexicon.md                  schema-v3.2.md               │
   │                                                              │
   ├────► canonical-id-derivation.md ◄───────────────── (V-IDS-DETERMINISTIC)
   │                                                              │
   ├────► canonical-fingerprint-normalisation.md ◄──── (V-FINGERPRINT-B)
   │            │                                                 │
   │            └────► scripts/lib/source_tokeniser.py ◄──────────┘
   │                       ▲
   │                       │
   │              tests/test_source_tokeniser.py
   │
   ├────► canonical-units.md ────────► glossary.md
   ├────► canonical-patterns.md ─────► glossary.md
   └────► source-content-handling.md ─► canonical-fingerprint-normalisation.md (Stage C)

glossary.md ──── indexes every field across schema + bundle
README.md ────── orients readers to all of the above
chef-app-spec.md ──── stage-4 consumer contract
```

Three observations about this graph:

1. **`rules.md` is the spine.** Every other normative file either references it or is referenced from it. Removing it leaves the bundle without a contract.
2. **Canonical profiles are fan-in points.** `canonical-id-derivation.md` and `canonical-fingerprint-normalisation.md` are each consumed by at least three other components (the prompt, the rules, the validator, and — for the fingerprint — the tokeniser library and tests). They are the load-bearing algorithms.
3. **The schema is consumed but does not reach back out.** It is the structural contract; everyone references it; it references no one. This is correct for a bottom-of-stack contract.

---

## 9. Why Each File is Necessary — Consequence of Removal

| File | What breaks if removed |
| --- | --- |
| `schema/cookpit-cooking-file-v3.2.json` | All structural validation. Every consumer must re-derive the shape from prose. |
| `docs/schema-v3.2.md` | Design rationale becomes inaccessible. Future schema authors lose continuity. |
| `bundle/v3.2/README.md` | Bundle has no entry point or reading order. |
| `bundle/v3.2/prompt.md` | The LLM has no method. Files become incoherent or refused. |
| `bundle/v3.2/rules.md` | No semantic contract. validation.md becomes unanchored; every criterion loses its rule reference. |
| `bundle/v3.2/lexicon.md` | Action text drifts to generic recipe-blog or robot-prose; persona is undefined. |
| `bundle/v3.2/glossary.md` | Field documentation is scattered across nine documents. |
| `bundle/v3.2/validation.md` | Validator has no specification; criteria become ad-hoc. |
| `bundle/v3.2/canonical-id-derivation.md` | ID generation is non-deterministic; the closed-world graph cannot be verified. |
| `bundle/v3.2/canonical-fingerprint-normalisation.md` | Source-faithfulness is unverifiable; V-FINGERPRINT-B cannot run. |
| `bundle/v3.2/canonical-units.md` | Units become unvalidated; shopping/scaling tooling cannot rely on them. |
| `bundle/v3.2/canonical-patterns.md` | AI re-derives structural decisions per recipe; consumers have no canonical reference for concurrency. |
| `bundle/v3.2/source-content-handling.md` | Stage C filtering becomes inconsistent; fingerprints diverge between implementations. |
| `scripts/validate_cookpit_v3.2.py` | No validation gate; no Stage-3 signing; the lifecycle stops at Stage 1. |
| `scripts/lib/source_tokeniser.py` | V-FINGERPRINT-B is unimplementable. |
| `tests/test_source_tokeniser.py` | Tokeniser regressions go unnoticed; the corpus's fingerprint conformance is unenforced. |
| `docs/chef-app-spec.md` | Runtime behaviour is undefined; every Chef app diverges. |

---

## 10. Summary

Cookpit v3.2 is a deliberately **layered architecture**. The schema is a structural contract; the bundle is a composable set of authoring inputs; the canonical profiles are deterministic algorithms; the validator enforces and signs; the chef-app spec defines the runtime. Every file has a single, well-defined role; cross-references are explicit and traceable; the four-stage lifecycle ties the pieces together end-to-end.

The architectural strength is the **separation between specification and implementation**: every algorithm exists first as a markdown profile (testable by humans), then as code (testable by machines), and the validator's checks point back to the profile by reference rather than re-stating it. This is what allows third-party implementations to plausibly conform — the contract is inspectable independent of the reference code.

A reader who has worked through this document should now be able to look at any v3.2 file, identify which lifecycle stage produced it, name which bundle file specifies each of its sections, and trace which validation criterion would catch any given malformation.


---

<!-- file: validation.md -->

# Validation

# Cookpit v3.2 — Validation Criteria

> The criteria a candidate v3.2 JSON-LD file must pass to be considered
> conformant. Each criterion references a rule from `rules.md` (or the v3.2
> JSON Schema), declares severity, and describes the check.
>
> The bundle accepts output from any LLM. Validation is the gate that
> tempers their enthusiasm: a file is accepted if and only if it passes the
> hard criteria below, regardless of which model produced it.
>
> A file is **valid v3.2** if and only if every criterion at severity
> `hard` passes. `soft` criteria are advisory: they surface in the validator
> report so the operator can decide whether to refine the prompt or accept
> the output. There is no third state.

---

## How validation runs

Validation is **stage 2 of the v3.2 lifecycle** (see `rules.md` A0). The
candidate file at the input is the AI Chef's stage-1 output: an
unauthenticated (`U`) file carrying `cookpit.attestation.status:
"unauthenticated"`. Validation produces a verdict, not a transformed
file. When the verdict is pass, the validator's stage-3 attestation step
(see `rules.md` R) consumes the same file to produce the authenticated
(`A`) form. Validation and attestation are conceptually distinct: the
former *checks*, the latter *certifies*. A stage-1 input that already
claims `status: "authenticated"` is malformed and is rejected at
V-LIFECYCLE-AI-EMITS-U.

The validator executes the following phases **in order**. The first failure
in phases 1–5 terminates the run and reports the failing criterion. Phase 6
collects soft findings without terminating.

1. **Parse.** The candidate output is parsed as JSON.
2. **Schema.** The parsed object is validated against
   `schema/cookpit-cooking-file-v3.2.json`.
3. **Lifecycle gating.** The candidate's `cookpit.attestation` block is
   inspected. The block must be the unauthenticated form per `rules.md`
   R2. The filename's `<status>` segment must be `U` and must agree with
   the internal `status` (V-LIFECYCLE-AI-EMITS-U,
   V-ATTESTATION-CONSISTENCY).
4. **Hard rule checks.** Every criterion of severity `hard` below is run.
5. **Source-faithfulness.** The strict quantitative fingerprint is computed
   from the source recipe and compared against the file's
   `cookpit.quantitativeFingerprint` (V-FINGERPRINT-B). This is the
   stage-1 → stage-2 integrity check across the source-faithfulness
   boundary.
6. **Soft findings.** Advisory checks run and are collected into the report.

The validator never modifies the file during validation. A repair loop may
take the validator's report and re-prompt the AI Chef with `"Fix only the
following criteria: …"`, but repair is a separate concern from validation.

When every hard criterion in phases 1–5 passes, the validator's
**stage-3 attestation step** runs:

- Canonicalise the file body with `cookpit.attestation.signature` cleared
  per `rules.md` R5.
- Compute the lowercase 64-hex SHA-256 file fingerprint.
- Replace `cookpit.attestation` with the authenticated form (status,
  issuer, validatorVersion, issuedAt, canonicalization, keyId,
  fileFingerprint, audit summary, signature).
- Sign the canonical bytes with the validator's private key.
- Rename the file's `<status>` segment from `U` to `A`.
- Return the authenticated file alongside the validation report.

Stage 3 is governed by the `V-FILE-FINGERPRINT` and `V-SIGNATURE`
self-checks below: before returning, the validator re-verifies that the
file it is about to emit verifies under R6. This guards against bugs in
the stamping pipeline.

---

## Severity

| Severity | Meaning |
| --- | --- |
| `hard` | Must pass for the file to be conformant v3.2. |
| `soft` | Advisory; the file is still conformant if it fails, but the operator should review. |

## Statuses

Every criterion produces one of the following statuses. Hard
criteria use the first five; soft criteria use `pass`, `warn` and
`info`.

| Status     | Severity (where used) | Meaning                                                                 |
| ---        | ---                   | ---                                                                     |
| `pass`     | hard, soft            | Criterion ran and passed.                                               |
| `fail`     | hard                  | Criterion ran and failed. Verdict → FAIL.                               |
| `warn`     | soft                  | Soft criterion produced a warning. Verdict unchanged.                   |
| `info`     | soft                  | Soft criterion produced informational data. Verdict unchanged.          |
| `deferred` | hard                  | Criterion's productised implementation is not yet shipped. The validator cannot perform the check today; a future PR will. Distinct from `skipped`. |
| `skipped`  | hard                  | Criterion needs an input the user did not supply (e.g. `--source <pdf>` for V-FINGERPRINT-B; `--pub-key` for V-SIGNATURE in default mode). Re-run with the input to clear. |
| `n/a`      | hard                  | Criterion does not apply to this file at this lifecycle stage (e.g. V-FILE-FINGERPRINT on a `U` file; V-INGREDIENT-ALTERNATIVE when no alternatives are declared). |

The verdict line is `PASS` when there are zero `fail` statuses and
`FAIL` otherwise. `deferred` and `skipped` statuses do NOT change the
verdict, but they are surfaced in the report's footer so a reader can
tell at a glance how many hard checks ran versus did not. A "PASS
verdict with N hard checks deferred or skipped" is structurally
different from "PASS verdict with all hard checks run and passed";
the validator's footer makes the distinction unambiguous.

---

## Criteria

### V-PARSE — JSON parse (hard)

The candidate output parses as a single JSON object. Multiple top-level
values, trailing commentary, markdown code fences, or non-JSON output all
fail this check.

### V-SCHEMA — JSON Schema (hard)

The parsed object validates against `schema/cookpit-cooking-file-v3.2.json`.
Schema errors are reported with their JSON pointer.

### V-LIFECYCLE-AI-EMITS-U — stage-1 input shape (hard) — rules A0.1, R2, R4

The candidate file presented for validation MUST be a stage-1 AI Chef
output:

- `cookpit.attestation` is present.
- `cookpit.attestation.status` equals `"unauthenticated"`.
- `cookpit.attestation` does NOT carry `signature`, `fileFingerprint`,
  `issuer`, `keyId`, `validatorVersion`, `issuedAt` or `canonicalization`.
- The filename's `<status>` segment, when present, equals `U`.

A file that already claims `status: "authenticated"`, or that carries any
of the authenticated-only fields, is malformed. The validator refuses to
re-validate an authenticated file; the user must strip the attestation
block back to the unauthenticated form and resubmit (rules.md R8).

### V-ATTESTATION-SHAPE — attestation block shape (hard) — rules R1, R2, R3

`cookpit.attestation.status` is one of `"unauthenticated"` or
`"authenticated"`. When `status` is `"unauthenticated"`, the block carries
no authenticated-only fields (per V-LIFECYCLE-AI-EMITS-U). When `status`
is `"authenticated"`, the block carries every required field listed in
rules.md R3. Inconsistent or partial blocks are a hard failure.

### V-ATTESTATION-CONSISTENCY — filename / status agreement (hard) — rules O1.2, R10

When the candidate is presented to the validator with a filename, the
filename's `<status>` segment matches `cookpit.attestation.status`:

- `…cpt.U.jsonld` ↔ `status: "unauthenticated"`.
- `…cpt.A.jsonld` ↔ `status: "authenticated"`.

Disagreement is a hard failure. The filename remains decorative; the
internal status remains the authoritative claim. The agreement check
catches accidental renames and tampering attempts that flip the filename
without producing a matching internal block.

### V-IDENTITY-A — `@type` and version (hard) — rules B1, B2

`@type` includes both `Recipe` and `cookpit:CookingFile`, and
`cookpit.version` equals `3.2.0`.

### V-IDENTITY-B — file id (hard) — rules B4, G1, G2

`cookpit.id` matches `^f[0-9a-f]{10}$`.

### V-IDENTITY-C — courses and difficulty (hard) — rules B5, B6

`cookpit.courses` is non-empty, has no duplicates, has at most three
entries, and is a subset of `[starter, main, dessert]`.
`cookpit.difficulty` is one of `easy`, `medium`, `hard`, `expert`.

### V-DURATION-A — phase nominal durations (hard) — rules C2, C4, Q1, Q3

For every declared phase (`cookpit.prepCook`, `cookpit.preCook`,
`cookpit.liveCook`), the phase's `nominalDuration` matches
`^[0-9]{2}:[0-9]{2}:[0-9]{2}$`. `cookpit.liveCook` is required;
`prepCook` and `preCook` are optional.
`cookpit.orchestration.timingBasis` is `cookTime`. `prepHandling` is
`preStartChecklist`. `runtimeOverruns` is `appOwned`.

### V-DURATION-B — phase-sum vs source (soft) — rule C2

When `cookpit.sourceTiming.cookTime` is present as ISO 8601 (`PT…`),
the ISO duration parses to the same `HH:MM:SS` as the SUM of all
declared phases' `nominalDuration` (or, for ranges in
`cookTimeText`, to the chosen composition per C3). A mismatch
suggests the phase decomposition was not source-derived.

### V-LANE-MODEL — fixed lane model (hard) — rule D1

`cookpit.laneModel` is the fixed primary/secondary/tertiary block defined in
the v3.2 spec. The block is compared by structural equivalence (lanes,
seconds, scopes, roles, default sounds), not by JSON byte equality.

### V-LANE-SCOPE — lane scope per course (hard) — rules D2, D3, D4

Every task on `A0` has `kind: "alarm"`. Every task on `S1/S2/S3` has
`course: "starter"`. Every task on `M1/M2/M3` has `course: "main"`. Every
task on `D1/D2/D3` has `course: "dessert"`. Tasks on secondary or tertiary
lanes (`S2/S3/M2/M3/D2/D3`) appear only when at least one other task on the
same course has a different lane in the same minute (i.e. there is a real
parallel workstream).

### V-LANE-SECONDS — seconds match lane (hard) — rule D5

For every task, the seconds component of `time` equals the canonical second
of its lane: `A0=:00`, `S1=:15`, `S2=:20`, `S3=:25`, `M1=:30`, `M2=:35`,
`M3=:40`, `D1=:45`, `D2=:50`, `D3=:55`.

### V-ALARMS — required global alarms per phase (hard) — rules E1, E2, E3, E5

For each declared phase, that phase's `tasks[]` contains exactly one
alarm at `00:00:00.A0` and exactly one alarm at the phase's
`nominalDuration` on `A0`. When the phase's `nominalDuration` is at
least `00:10:00`, exactly one alarm at
`(nominalDuration − 10 minutes).A0` exists in that phase. Every A0
task has `kind: "alarm"`. A0 alarms do not require `timingBasis`.

### V-ALARM-OPTIONAL — optional 5-minute alarm (soft) — rule E4

Within a phase, at most one `(nominalDuration − 5 minutes).A0` alarm
exists; if present it has `kind: "alarm"`. Multiple 5-minute alarms
in a single phase are a soft fail.

### V-PREP — prep is untimed (hard) — rules F1, F2, F3

No task in `tasks` describes prep that the source labels as prep time.
Pre-start preparation appears in `cookpit.prerequisites`. Live "during cook"
prep that the source assigns to the cook window is allowed as a normal
timed task. `cookpit.orchestration.startEnabledBy` is
`allPrerequisitesConfirmed`.

### V-IDS-FORMAT — id pattern (hard) — rules G1, G2

Every id in the file matches `^[a-z][0-9a-f]{10}$`. The leading prefix
matches the entity type per G2.

### V-IDS-DETERMINISTIC — id derivation (hard) — rule G3

For every id in the file, the validator recomputes the deterministic id from
`(entityType, canonicalContent, canonicalPosition)` per the canonical
generation profile and confirms it matches the id in the file. Mismatches
indicate non-deterministic generation and are a hard fail.

### V-IDS-UNIQUE — id uniqueness (hard) — rule G4

No two entities share the same id within the file.

### V-REFS-CLOSED — resource closure (hard) — rules H1–H7, A2, Q5

Every cross-reference resolves to a declared entity of the matching
type, with phase-scoping where applicable:

- `ingredientRefs` → `cookpit.ingredients[].id` (must start with `i`);
- `equipmentRefs` → `cookpit.equipment[].id` (must start with `e`);
- `utensilRefs` → `cookpit.utensils[].id` (must start with `u`);
- `sundryRefs` → `cookpit.sundries[].id` (must start with `s`);
- task `processRefs` → a `processes[].id` declared IN THE SAME PHASE
  (must start with `p`); cross-phase `processRefs` are forbidden;
- process `startTask` and `endTask` → a `tasks[].id` declared IN THE
  SAME PHASE (must start with `t`); cross-phase boundary tasks are
  forbidden;
- hotspot `taskRefs` → any declared phase's `tasks[].id` (must start
  with `t`); hotspots are file-level metadata and may target any
  phase.

A reference whose target is missing, whose prefix mismatches the
entity type, or which crosses a phase boundary against the rule
above is a hard failure.

### V-REFS-PHANTOM — no undeclared resources in actions (soft) — rule H8

Heuristic check: task `action` strings are scanned for mentions of common
tools (`thermometer`, `blender`, `scales`, …) and the validator reports any
mention whose corresponding declared resource list does not contain a
plausible match. Heuristic only; the operator decides whether to act.

### V-REFS-COVERAGE — declared resources are used (soft) — rule H9

Every declared resource is referenced by at least one task, process or
prerequisite. Resources that are listed but never referenced are reported
informationally.

### V-TASKS-SHAPE — task required fields (hard) — rules I1, I2

Every task has `id`, `time`, `kind`, `action`. Every task whose `kind` is
not `alarm` additionally has a non-empty `timingBasis`. `kind` is one of
`alarm`, `alert`, `update`. Only `alarm` tasks appear on `A0`; `alert` and
`update` tasks appear only on course lanes.

### V-TASKS-TIME — task time format (hard) — rule I3

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

### V-TASKS-WITHIN — tasks fit within phase duration (hard) — rule I5

For every task, `HH:MM:SS` is less than or equal to its phase's
`nominalDuration`. The single exception is the phase's time-up
alarm, which equals the phase's `nominalDuration` exactly.

### V-TASKS-ORDER — task ordering per phase (hard) — rule I6

Within each phase's `tasks[]`, the array is sorted by `time`, ties
broken by `lane` then `id`. Cross-phase ordering is not meaningful
because each phase has its own clock.

### V-TASKS-LANGUAGE — culinary language (soft) — rule I7

Task `action` strings do not contain UI verbs (`tap`, `swipe`, `confirm`,
`press`, `done`, `next`, `continue`). Heuristic check; reported as soft so
the operator can confirm wording quality without rejecting the file outright.

### V-TIMING-BASIS — non-alarm tasks have a basis (hard) — rules I1, I8

Every non-alarm task has a `timingBasis` whose `basis` is one of
`sourceExactDuration`, `sourceRangeMinimum`, `sourceRangeTarget`,
`sourceCookTimeEndpoint`, `sourceOrder`, `sourceMeanwhile`,
`sourceOutcomeCue`, `sourceImpliedDeadline`, `canonicalProcessEstimate`.
The `source` field is a non-empty string. When `basis` is
`sourceImpliedDeadline`, `offsetFrom` references an existing task id and
`offset` is a negative ISO 8601 duration recording the prep duration that
was deduced from the deadline.

### V-TIMING-NONRANDOM — no cosmetic spacing (soft) — rules A1, I9

Heuristic check: task gaps are scanned for suspiciously regular spacing
(e.g. every task placed exactly 30 seconds apart with no source-derived
explanation). Suspicious patterns are reported soft so the operator can
review the prompt's effectiveness.

### V-PROCESSES — process consistency (hard) — rules J1, J2, J3, J4

Every process's `startTask` and `endTask` exist in the SAME phase's
`tasks[]`. The interval between their `time` values, ignoring lane
seconds, equals the process's `duration.target` parsed as ISO 8601.
`completion.type` is one of `timed`, `sensory`, `temperature`,
`compound`. Within a phase, processes are listed in the order their
`startTask` occurs.

### V-PHASES-PRESENT — phase declaration (hard) — rule Q1

`cookpit.liveCook` is required and is a phase block with `label`,
`nominalDuration`, `tasks[]` and optional `processes[]` and
`completion`. `cookpit.prepCook` and `cookpit.preCook` are optional;
when present they have the same shape. No other phase keys are
permitted.

### V-PHASE-IDS — phase identity (hard) — rule Q2

`cookpit.prepCook.id`, when present, matches `^y[0-9a-f]{10}$`.
`cookpit.preCook.id`, when present, matches `^z[0-9a-f]{10}$`.
`cookpit.liveCook` does NOT carry an `id` field; its identity is
the file's `cookpit.id`.

### V-PHASE-CONTINUITY — phase shape (hard) — rule Q3

For every declared phase: `label` is a non-empty string,
`nominalDuration` is `HH:MM:SS`, `tasks[]` is non-empty.
`processes[]` (when present) lives inside the phase block — there
are no top-level processes outside any phase.

### V-PHASE-ORDER — phase document order (soft) — rule Q4

When more than one phase is declared, their JSON-document order in
`cookpit` follows the fixed canonical layout: `prepCook`,
`preCook`, `liveCook`. This is a human-readability convention,
not a runtime sequencing claim — at runtime `prepCook` and
`preCook` are independent (they may run concurrently or
sequentially; the file does not encode that choice), and
`liveCook` is strictly downstream of both. Out-of-order document
layout is a soft warning so the file remains visually consistent
with the rest of the corpus.

### V-METHOD-ORDER — method order preservation (hard) — rule A1, A2

Tasks on each course lane appear in an order that is a topological extension
of the source method order. The validator extracts numbered or sequential
method steps from the source and confirms that the corresponding tasks in
the same course do not invert the source order. Violations are hard
failures.

### V-SOURCE-COVERAGE — source durations covered (hard) — rule K1

Every numeric duration stated in the source method (e.g. "simmer for 20
minutes", "bake 25–30 minutes") is reflected in the plan by a process or by
a task pair whose interval matches the stated duration (or its chosen
range minimum per C3). Missing durations are hard failures.

### V-SOURCE-TEMPS — temperatures and gas marks present (hard) — rule K1

Every temperature, oven setting, gas mark or numeric heat instruction in the
source is present somewhere in the file (typically in a task `action` or in
the relevant ingredient note). Omissions are hard failures.

### V-FINGERPRINT-A — fingerprint shape (hard) — rules K3, K4, K5

`cookpit.quantitativeFingerprint` is present, has `type: "strict"`,
`basis: "ingredients-and-method-active-numbers"`,
`normalization: "cookpit-active-number-sequence-v3.2.0"`,
a `sequence` matching `^[0-9]+(-[0-9]+)*$`, and a `hash` with
`algorithm: "sha256"` and `value` matching `^[0-9a-f]{64}$`.

### V-FINGERPRINT-B — fingerprint matches source (hard) — rules K3–K5

The validator independently extracts the active-number sequence from the
source recipe per the canonical normalization rules
(`canonical-fingerprint-normalisation.md`, executable embodiment at
`scripts/lib/source_tokeniser.py`) and confirms:

- the file's `sequence` equals the validator's computed sequence; and
- the file's `hash.value` equals the SHA-256 hex digest of that sequence.

A mismatch on either is a hard failure (`STATUS_FAIL`). Without the
`--source <pdf|text>` flag the criterion reports `STATUS_SKIPPED` —
the validator cannot verify source faithfulness without the source.
For an image-only PDF whose extracted text is empty, the criterion
also reports `STATUS_SKIPPED` with detail explaining why.

### V-NO-RUNTIME — forbidden runtime fields absent (hard) — rules L1, L2

The validator walks the entire JSON tree and confirms no field name from
the forbidden list appears: `urgency`, `priority`, `progress`,
`completionState`, `checkboxState`, `actualStartTime`, `actualFinishTime`,
`overdue`, `overdueState`, `focusTask`, `bannerColor`, `timerColor`,
`overrunState`, `queuePosition`, `scrollPosition`, plus any field whose
name matches the heuristic pattern of runtime state (`/(actual|current|live|overdue|focus|banner|timer|overrun|queue|scroll)[A-Z]/`).

### V-GENERATION — generation metadata (hard) — rules M1–M5

`cookpit.generation` is present with `profile` set, `idPolicy` =
`deterministic-type-prefixed-10-hex`, `timingPolicy` =
`source-derived-deterministic-optimal`, `resourcePolicy` =
`closed-world-declared-resources`, and `randomTimingAllowed` = `false`.

### V-OPTIONAL-COVERAGE — optional ingredients exempt from coverage (info) — rule N1

The soft resource-coverage check (V-REFS-COVERAGE) ignores ingredients
marked `optional: true`. Optional ingredients with no task or process
reference are reported informationally only.

### V-EQUIPMENT-POWER — equipment power enum (hard) — rule N2

When present, `equipment[].power` is one of `electric`, `gas`, `induction`,
`none`. Other values are a hard failure.

### V-TASK-SOUND — task sound enum (hard) — rule N3

When present, `task.sound` is one of `bell`, `klaxon`, `chime`, `tick`.

### V-PREREQ-LEADTIME — prerequisite leadTime is ISO 8601 (hard) — rule N4

When present, `prerequisites.*[].leadTime` matches an ISO 8601 duration
(`^P…`).

### V-PREREQ-INGREDIENT-REFS — prerequisite ingredient closure (hard) — rule N5

When `prerequisites.*[].ingredientRefs` is present, every id starts with
`i` and exists in `cookpit.ingredients[].id`. Wrong-type or dangling refs
are hard failures (same enforcement as V-REFS-CLOSED).

### V-INGREDIENT-ALTERNATIVE — alternative shape (hard) — rule N6

When present, `ingredient.alternative` carries a non-empty `text` string
and may carry `quantity`, `unit`, `metricQuantity`, `metricUnit` matching
the same shapes used on the primary ingredient.

### V-LEX-FORBIDDEN — forbidden lexicon terms (soft) — rule P8

Heuristic scan of every field in lexicon scope (per rule P2) for forbidden
tokens drawn from `lexicon.md` §7.1 (hedgers), §7.2 (warmth and marketing),
§7.3 (filler) and §7.4 (vague outcomes without sensory companion). Each
hit is reported as a soft warning with the field path and the offending
token. The validator does not strip or rewrite text.

### V-LEX-IMPERATIVE — imperative form on actions (soft) — rules P3, I7

Heuristic check that every `tasks[].action` string and every prerequisite
item carrying an active cooking action begins with an imperative verb.
Strings beginning with second-person pronouns (`you`, `your`, `you'll`,
`you're`) or with hedger constructions (`make sure to`, `you'll want to`,
`feel free to`) are flagged as soft warnings.

### V-LEX-SENSORY — sensory completion cues (soft) — rule P7

For every `completion` whose `type` is not `timed`, the `cue` (or each
condition's `cue` for `compound`) is scanned for at least one sensory
token from `lexicon.md` §6 or a clear synonym. Cues that contain only
abstract outcomes (`done`, `ready`, `right`) without a sensory anchor are
flagged as soft warnings.

### V-LEX-PROCESS-LABEL — process-label grammar (soft) — rule P11

Heuristic check that every `processes[].label` reads as a short
present-continuous noun phrase (e.g. ends in `ing` and is three to five
words long, or is a recognised noun-phrase pattern). Imperative-form
labels (`Reduce the sauce`) are flagged as soft warnings.

### V-LEX-PERSONA-DRIFT — persona drift (soft) — rules P1, P8

Heuristic scan for register drift away from the rebel chef persona:
brigade-formal language patterns (passive voice on cooking actions, "take
care to", "in order to"), recipe-blog warmth (multiple warmth tokens in a
single field), and headmaster register (`make sure`, `you'd better`,
`if you don't`). Findings are reported as soft warnings to drive prompt
refinement.

### V-FILENAME — filename methodology consistency (soft) — rules O1–O8

When the candidate file is presented to the validator with a filename, the
validator checks:

- the filename matches the pattern `<slug>.v3.2.cpt.<A|U>.jsonld`;
- the `<slug>` agrees with the slug derived from `name` per O2;
- the `<status>` segment is `A` or `U`;
- the `<status>` segment agrees with `cookpit.attestation.status`
  (this overlaps V-ATTESTATION-CONSISTENCY at hard severity; here it is
  reported as a soft warning when filename and content are absent or
  ambiguous);
- `cookpit.version` starts with `3.2.`.

A filename without the `<status>` segment (legacy `…cpt.jsonld` form per
O8) is reported as a soft warning recommending the explicit `U` flag,
but does not prevent conformance. When no filename is available the
criterion produces an `info` status only.

### V-FILE-FINGERPRINT — file fingerprint consistency (hard, stage 3) — rule R5

For a file the validator is about to emit at stage 3, the
`cookpit.attestation.fileFingerprint` MUST equal the SHA-256 digest of
the canonicalised file body computed with both
`cookpit.attestation.signature` AND
`cookpit.attestation.fileFingerprint` cleared, using the canonicalisation
profile named in `cookpit.attestation.canonicalization`.

This criterion is checked twice:

- **Pre-emit (validator self-check).** Before returning a stage-3
  authenticated file, the validator recomputes the fingerprint and
  confirms it matches the value it is about to embed. A mismatch
  indicates a bug in the stamping pipeline; the validator MUST refuse
  to emit.
- **Post-load (consumer-side).** A stage-4 consumer recomputes the
  fingerprint on every load (R6 step 4). A mismatch indicates the file
  has been altered since stamping; the consumer MUST treat the file as
  untrusted (rules.md R7).

### V-SIGNATURE — signature verification (hard, stage 3 / stage 4) — rules R3, R6

For a file claiming `cookpit.attestation.status: "authenticated"`, the
signature MUST verify under the public key identified by
`cookpit.attestation.keyId`, over the canonical bytes produced by R5
(canonicalised file body with `signature` cleared).

This criterion is checked twice:

- **Pre-emit (validator self-check).** Before returning a stage-3
  authenticated file, the validator verifies its own signature over the
  canonical payload. Failure indicates a bug in the signing pipeline;
  the validator MUST refuse to emit.
- **Post-load (consumer-side).** A stage-4 consumer verifies the
  signature on every load (R6 step 5) using its pinned public-key set.
  Verification failure is a hard rejection; the consumer MUST NOT
  silently downgrade the file to `U` semantics.

Stripping or substituting `issuer`, `validatorVersion`, `issuedAt`,
`canonicalization`, `keyId`, `fileFingerprint`, or any field elsewhere in
the file changes the canonical bytes and invalidates the signature.

---

## Reporting

Each criterion produces one of:

- `pass`
- `fail` (with a JSON Pointer to the offending location and a one-line message)
- `warn` (soft criteria only)
- `info` (advisory observations the validator surfaces without judgement)

The full report is shaped as:

```json
{
  "version": "3.2.0",
  "verdict": "pass | fail",
  "summary": { "hardFailures": 0, "softWarnings": 0, "infos": 0 },
  "criteria": [
    {
      "id": "V-LANE-SECONDS",
      "rule": "D5",
      "severity": "hard",
      "status": "pass",
      "details": []
    }
  ]
}
```

`verdict` is `pass` if and only if every `hard` criterion is `pass` and the
JSON Schema validation succeeded.

---

## Determinism check (optional)

A determinism check is run separately from validation. It re-runs the
generator with the same prompt, rules, schema and source recipe at low
temperature `n` times (default 3) and compares the resulting files using a
canonical diff. Stable runs produce byte-identical canonical output. Drift
on any field other than free-form `action` wording is a soft warning; drift
on times, ids, lanes, references, or the fingerprint is a hard failure of
the determinism check (separate from per-file validation).

---

## Conformance and authentication

A file is **conformant v3.2** if and only if every `hard` criterion above
is `pass` and the JSON Schema validates. `soft` warnings do not prevent
conformance; they exist to drive prompt refinement and operator review.

A file is **authenticated v3.2** if, in addition, it has been processed
through stage-3 attestation (per rules.md R) by the canonical validator,
verifies under V-FILE-FINGERPRINT and V-SIGNATURE against the published
public key, carries `cookpit.attestation.status: "authenticated"`, and
uses the `A` filename flag. Authentication is a stronger property than
conformance: every authenticated file is conformant, but a stage-1 AI
output may be conformant (passes every hard criterion) without ever
being authenticated, simply because it has not been put through the
canonical validator's stage-3 stamp.


---

<!-- file: glossary.md -->

# Glossary

# Cookpit v3.2 — Definitive Glossary

> The authoritative field-by-field reference for the v3.2 schema.
> Treats v3.2 as a clean-slate format (no backward-compatibility
> ballast). Every value defined by `schema/cookpit-cooking-file-v3.2.json`
> is documented here: name, JSON type, purpose within the v3.2
> cooking-file model, and where applicable the constant set, regex,
> or canonical reference.
>
> When a field has been the subject of a published canonical profile,
> a published rule, or a corpus-derived pattern, the glossary points
> at the authoritative document rather than restating it. The
> glossary is therefore intentionally compact — it indexes the v3.2
> world, it does not re-explain it.
>
> Cross-references in `→ doc#section` form point at the bundle's
> canonical documents.

---

## Lifecycle context

Every v3.2 cooking file passes through four stages
(`rules.md` A0):

1. **Generation.** AI Chef emits an unauthenticated (`U`) file.
2. **Validation.** Validator runs the file against `validation.md`.
3. **Attestation.** Validator stamps a passed file, replacing the
   attestation block with the authenticated form and flipping the
   filename flag to `A`.
4. **Consumption.** Chef app or other consumer verifies the file's
   signature and runs the plan.

The schema fields documented below appear at different stages. Two
fields in particular are stage-bound:

- `cookpit.quantitativeFingerprint` (§2.17, §12) — the **stage-1 source
  fingerprint**. AI populates it; validator confirms it at stage 2.
- `cookpit.attestation` (§2.20, §16) — the **stage-1 unauthenticated
  marker** that the validator replaces with the **stage-3 authenticated
  attestation** carrying the file fingerprint and signature consumed at
  stage 4.

These two fingerprints are not redundant — they answer different
integrity questions at different stages (`rules.md` A0.6).

---

## How to read this glossary

Every entry follows the same structure:

| Heading             | Meaning                                                         |
| ---                 | ---                                                             |
| **Name**            | The JSON path used in the schema                                |
| **Type**            | JSON type (`string`, `number`, `array<T>`, `object`, etc.)      |
| **Required**        | Whether the field is required at its position in the schema     |
| **Constraint**      | Regex, enum, range, or `$ref` referenced by the schema         |
| **Purpose**         | What this field carries; what consumes it; what role it plays   |
| **Reference**       | Pointer to the canonical document if one exists                 |
| **Example**         | A representative value drawn from the walked corpus where appropriate |

Schema paths use dotted notation. Array element fields are written as
`<array>[].<field>`. `oneOf` branches are numbered.

---

## Section 1 — Top-level (schema.org pass-through + cookpit anchor)

### 1.1 `@context`
- **Type**: `string` | `array` | `object`
- **Required**: yes
- **Constraint**: must include `https://schema.org/` and a `cookpit`
  namespace mapping
- **Purpose**: JSON-LD context anchor. The `https://schema.org/`
  declaration gives the file schema.org Recipe semantics for any
  consumer that doesn't know about cookpit. The `cookpit` namespace
  mapping (canonical URI `https://cookpit.org/ns/v3.2#`) gives
  cookpit-aware consumers access to the `cookpit:` typed extension.
- **Example**: `["https://schema.org/", { "cookpit": "https://cookpit.org/ns/v3.2#" }]`

### 1.2 `@type`
- **Type**: `array<string>` (length ≥ 2)
- **Required**: yes
- **Constraint**: MUST include both `Recipe` and `cookpit:CookingFile`
- **Purpose**: declares the file as both a schema.org Recipe (for
  schema.org-aware consumers like Google Search) and a v3.2
  CookingFile (for the Chef app and the cookpit corpus). The dual
  declaration is what makes the file portable across the two worlds.
- **Reference**: rules.md B1
- **Example**: `["Recipe", "cookpit:CookingFile"]`

### 1.3 `$schema`
- **Type**: `string` (URI)
- **Required**: optional but expected
- **Purpose**: identifies the v3.2 JSON Schema URL for tooling that
  resolves schemas by URI. Convenience field.
- **Reference**: rules.md B3
- **Example**: `https://cookpit.org/v3.2/schema.json`

### 1.4 `name`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: the recipe's display name. Pass-through to
  schema.org `Recipe.name`. Drives the file's deterministic id
  (the `f…` id is hashed from this value via the canonical id
  derivation profile).
- **Reference**: → canonical-id-derivation.md §3.1
- **Example**: `"Pork fillet, braised cheeks and pork belly"`

### 1.5 `description`
- **Type**: `string`
- **Required**: optional
- **Purpose**: free-text description for human readers and schema.org
  consumers. Lexicon does NOT apply to this field; it is a schema.org
  pass-through that may carry source warmth verbatim.
- **Reference**: lexicon.md §1 scope table

### 1.6 `image`
- **Type**: `string` (URI) | `array<string>`
- **Required**: optional
- **Purpose**: schema.org image URL(s) for the dish. Single URI or
  array.

### 1.7 `author`
- **Type**: `object` | `string`
- **Required**: optional
- **Purpose**: schema.org author. Either a `Person` / `Organization`
  object with `@type` and `name`, or a free-text string.
- **Example**: `{ "@type": "Person", "name": "Stephen Crane" }`

### 1.8 `datePublished`
- **Type**: `string`
- **Required**: optional
- **Purpose**: schema.org publication date. Free-text — the schema
  doesn't restrict the format.

### 1.9 `prepTime`
- **Type**: `string` (ISO 8601 duration)
- **Required**: optional
- **Constraint**: pattern starts with `P`
- **Purpose**: schema.org prep duration. Reflects the source's
  prep-time field. The cookpit-internal authoritative copy is at
  `cookpit.sourceTiming.prepTime`.
- **Example**: `"PT45M"`

### 1.10 `cookTime`
- **Type**: `string` (ISO 8601 duration)
- **Required**: optional
- **Constraint**: pattern starts with `P`
- **Purpose**: schema.org cook duration. Drives the SUM of all
  declared phases' `nominalDuration` per rule C2 (live timer is cook
  time only, never total). The cookpit-internal copy is at
  `cookpit.sourceTiming.cookTime`.

### 1.11 `totalTime`
- **Type**: `string` (ISO 8601 duration)
- **Required**: optional
- **Constraint**: pattern starts with `P`
- **Purpose**: schema.org total duration. Informational only —
  v3.2's live timer is bound to cook time, not total time.

### 1.12 `recipeYield`
- **Type**: `string` | `number`
- **Required**: optional
- **Purpose**: schema.org yield. Either a number ("4") or a phrase
  ("Serves 6-8"). Free-text.

### 1.13 `recipeCategory`
- **Type**: `string`
- **Required**: optional
- **Purpose**: schema.org category. Free-text.
- **Example**: `"Main"`, `"Dessert"`, `"Starter"`

### 1.14 `recipeCuisine`
- **Type**: `string`
- **Required**: optional
- **Purpose**: schema.org cuisine. Free-text. Lexicon §8
  source-faithful preservation rules apply to in-scope text but the
  cuisine label itself is metadata.
- **Example**: `"Italian"`, `"French"`, `"Chinese"`

### 1.15 `keywords`
- **Type**: `string` (comma-separated)
- **Required**: optional
- **Purpose**: schema.org keywords. Comma-separated. For dietary
  tags, prefer `cookpit.dietary[]` (typed, validated) over keywords
  string.

### 1.16 `nutrition`
- **Type**: `object`
- **Required**: optional
- **Purpose**: schema.org `NutritionInformation` object. Pass-through
  to schema.org consumers.

### 1.17 `recipeIngredient`
- **Type**: `array<string>` (length ≥ 1)
- **Required**: yes
- **Purpose**: schema.org ingredient list — the SOURCE recipe's
  ingredient lines preserved verbatim. The chef-detective's cookpit-
  typed view is at `cookpit.ingredients[]`. Both should be present
  in every file; this is the source-faithful pass-through copy.
- **Reference**: lexicon.md §1 scope table — lexicon does NOT apply
- **Example**: `["1 kg beef fillet", "250 g mushrooms", ...]`

### 1.18 `recipeInstructions`
- **Type**: `array` of `recipeInstructionItem` ($def)
- **Required**: optional
- **Purpose**: schema.org method steps preserved verbatim from the
  source. The chef-detective's cookpit-typed plan is split across
  declared phases (`cookpit.prepCook.tasks[]`,
  `cookpit.preCook.tasks[]`, `cookpit.liveCook.tasks[]`). Source
  typos and culinary tips remain visible here per the
  source-content-handling rules; the typed plan filters them.
- **Reference**: → source-content-handling.md
- **Example**: items shape per `recipeInstructionItem` $def

### 1.19 `cookpit`
- **Type**: `object` ($ref `cookpit`)
- **Required**: yes
- **Purpose**: the v3.2 extension block. Holds every cookpit-typed
  field below (sections 2-12).

---

## Section 2 — `cookpit` block (top-level extension)

### 2.1 `cookpit.version`
- **Type**: `string`
- **Required**: yes
- **Constraint**: pattern `^3\.2\.\d+$`
- **Purpose**: the cookpit cooking-file format version. Always
  `3.2.0` for files conforming to this glossary.
- **Reference**: rules.md B2

### 2.2 `cookpit.id`
- **Type**: `string`
- **Required**: yes
- **Constraint**: `$fileId` pattern `^f[0-9a-f]{10}$`
- **Purpose**: the file's deterministic id. Derived from the
  recipe's `name` field via the canonical id derivation profile.
- **Reference**: → canonical-id-derivation.md §3.1

### 2.3 `cookpit.courses`
- **Type**: `array<string>`
- **Required**: yes
- **Constraint**: 1-3 items, no duplicates, each from `["starter", "main", "dessert"]`
- **Purpose**: declares which course(s) this file's plan covers.
  Drives lane-scope (`S1/S2/S3` lanes are valid only when `"starter"`
  is in courses, etc.).
- **Reference**: rules.md B5

### 2.4 `cookpit.difficulty`
- **Type**: `string` (enum)
- **Required**: yes
- **Constraint**: one of `["easy", "medium", "hard", "expert"]`
- **Purpose**: declares the dish's difficulty band. Drives Chef-app
  filtering and progressive-disclosure UX.
- **Reference**: rules.md B6

### 2.5 `cookpit.sourceTiming`
- **Type**: `object`
- **Required**: optional
- **Purpose**: preserves the source recipe's stated timing facts
  verbatim. Carries either ISO durations (when the source uses
  ISO-compatible phrasing) or text equivalents (when the source uses
  ranges, prose, or open bounds).

#### 2.5.1 `cookpit.sourceTiming.prepTime`
- **Type**: ISO 8601 duration string
- **Purpose**: source prep time, ISO form.

#### 2.5.2 `cookpit.sourceTiming.cookTime`
- **Type**: ISO 8601 duration string
- **Purpose**: source cook time, ISO form. Drives the SUM of all
  declared phases' `nominalDuration` (rule C2). For files with only
  a `liveCook` phase this is the `liveCook.nominalDuration` itself.

#### 2.5.3 `cookpit.sourceTiming.totalTime`
- **Type**: ISO 8601 duration string
- **Purpose**: source total time, ISO form.

#### 2.5.4 `cookpit.sourceTiming.prepTimeText`
- **Type**: free-text string
- **Purpose**: when the source uses prose (e.g. `"Less than 30 mins"`)
  or a range (`"15 mins - 20 mins"`), the prose is preserved here.

#### 2.5.5 `cookpit.sourceTiming.cookTimeText`
- **Type**: free-text string
- **Purpose**: cook-time prose. Used when the source has a range
  (`"30 mins to 1 hour"`) or open bound (`"Over 2 hours"`); the
  chosen phase decomposition (the `nominalDuration` values across
  declared phases) is justified per rules.md C3.

#### 2.5.6 `cookpit.sourceTiming.totalTimeText`
- **Type**: free-text string

### 2.6 (removed in v3.2 three-phase) `cookpit.nominalCookDuration`

This top-level field has been removed. Each declared phase now
carries its own `nominalDuration` inside the phase block — see
section 14 below for `cookpit.prepCook`, section 15 for
`cookpit.preCook`, and section 16 for `cookpit.liveCook`. The total
live runtime of the file is the SUM of all declared phases'
`nominalDuration` values.

### 2.7 `cookpit.laneModel`
- **Type**: `object` ($ref `laneModel`)
- **Required**: yes
- **Purpose**: the fixed primary/secondary/tertiary course lane
  block. Same shape in every v3.2 file. Provides the alarm lane (A0
  at second :00) and three lanes per course (e.g. M1/M2/M3 at
  seconds :30/:35/:40).
- **Reference**: rules.md D1, D5; → canonical-patterns.md §1

### 2.8 `cookpit.orchestration`
- **Type**: `object` ($ref `orchestration`)
- **Required**: yes
- **Purpose**: declares orchestration semantics — how the live
  timer relates to source timing, how prep is handled, how runtime
  overruns are managed.

### 2.9 `cookpit.prerequisites`
- **Type**: `object` ($ref `prerequisites`)
- **Required**: optional
- **Purpose**: pre-Start checklist groups. Six groups: ingredients,
  equipment, utensils, sundries, skills, hotspots, notes. Pre-cook
  prep that satisfies its deadline trivially is detective-promoted
  here; the Chef app blocks Start until all included items are
  confirmed.
- **Reference**: rules.md F

### 2.10 `cookpit.ingredients`
- **Type**: `array<ingredient>`
- **Required**: optional but expected
- **Purpose**: the chef-detective's typed ingredient view. Each
  entry mirrors a `recipeIngredient` line but is structurally
  encoded with quantity, unit, section, role, and optional
  primitives (alternative, choices, splits, extras, depth).

### 2.11 `cookpit.equipment`
- **Type**: `array<equipment>`
- **Required**: optional but expected
- **Purpose**: declared equipment (vessels, ovens, hobs, mixers,
  fryers). Closed-world: every equipment-ref in tasks/processes
  resolves to a declared item.

### 2.12 `cookpit.utensils`
- **Type**: `array<utensil>`
- **Required**: optional but expected
- **Purpose**: declared utensils (knives, spoons, scales, jugs).

### 2.13 `cookpit.sundries`
- **Type**: `array<sundry>`
- **Required**: optional
- **Purpose**: declared sundries — consumables that aren't
  ingredients (cling film, parchment, foil, kitchen paper).

### 2.14 `cookpit.prepCook`
- **Type**: `object` ($ref `phaseBlock` + required `id` of pattern `^y[0-9a-f]{10}$`)
- **Required**: optional — declared only when the source establishes
  a discrete TIMED active-prep window BEFORE the cook day (pressing
  meat under weights, salt-curing, marinating with active
  monitoring).
- **Purpose**: the active timed prep phase. Carries its own clock,
  lanes, tasks, processes and completion cue. Independent of
  `cookpit.preCook` at runtime — the Chef app starts the prepCook
  A0 timer when file-level prerequisites are confirmed; prepCook
  may run concurrently with or sequentially before/after preCook
  (Q4). prepCook is NOT a relocation target for prep that F2 places
  inside `cookpit.liveCook` (F5).
- **Reference**: rules.md F3, F5, Q1-Q9; → canonical-patterns.md §8

### 2.15 `cookpit.preCook`
- **Type**: `object` ($ref `phaseBlock` + required `id` of pattern `^z[0-9a-f]{10}$`)
- **Required**: optional — declared when the source cooks a
  mainstay component ahead of final assembly (a slow braise whose
  product is plated, a meringue base, a poached salmon for a pâté).
- **Purpose**: the pre-cook phase for cooked mainstay components.
  Internal lane concurrency handles parallel processes (cheeks on
  M1, confit on M2 within preCook). Independent of
  `cookpit.prepCook` at runtime — the Chef app starts the preCook
  A0 timer when file-level prerequisites are confirmed; preCook may
  run concurrently with or sequentially before/after prepCook (Q4).
- **Reference**: rules.md F3, Q1-Q9; → canonical-patterns.md §8

### 2.16 `cookpit.liveCook`
- **Type**: `object` ($ref `phaseBlock`, NO `id` field of its own)
- **Required**: yes — every v3.2 file declares liveCook.
- **Purpose**: the final-assembly cook phase ending in serving. The
  dish reaches the plate at liveCook time-up. liveCook borrows the
  file's `cookpit.id` (`f…`) as its identity — the file IS the live
  cook. Strictly downstream of `cookpit.prepCook` and
  `cookpit.preCook` at runtime — liveCook becomes available only
  when the LAST of the declared upstream phases fires its A0
  time-up alarm; liveCook NEVER overlaps prepCook or preCook (Q4).
  Live prep that F2 places inside the cook window stays in
  liveCook; the presence of a prepCook block does not relocate it
  out (F5).
- **Reference**: rules.md F2, F5, Q1-Q9; the canonical default for
  the vast majority of recipes.

### 2.17 `cookpit.quantitativeFingerprint`
- **Type**: `object` ($ref `quantitativeFingerprint`)
- **Required**: optional but expected
- **Purpose**: the strict active-number fingerprint of the SOURCE
  recipe's numeric content (the **stage-1 source fingerprint**).
  Computed by the AI at generation time from the source recipe;
  re-checked by the validator at stage 2 (V-FINGERPRINT-B). Identifies
  the file's source numeric skeleton. Distinct from the file
  fingerprint that lives at `cookpit.attestation.fileFingerprint` and
  is computed by the validator at stage 3 (rules.md A0.6).
- **Reference**: → canonical-fingerprint-normalisation.md, rules.md K, A0.6

### 2.18 `cookpit.dietary`
- **Type**: `array<string>` (enum)
- **Required**: optional
- **Constraint**: each value from a 20-element vocabulary
  (vegetarian, vegan, pescatarian, dairy-free, egg-free, nut-free,
  peanut-free, gluten-free, wheat-free, soy-free, shellfish-free,
  low-sugar, low-sodium, low-fat, low-carb, high-protein, halal,
  kosher, pregnancy-friendly, alcohol-free)
- **Purpose**: structural dietary tags. Replaces lossy `keywords`
  storage for downstream filtering / allergen audit / shopping list.

### 2.19 `cookpit.generation`
- **Type**: `object` ($ref `generation`)
- **Required**: yes
- **Purpose**: generation metadata declaring the canonical profile
  the file was authored against. Drives validator behaviour.

### 2.20 `cookpit.attestation`
- **Type**: `object` ($ref `attestation`)
- **Required**: yes
- **Purpose**: the lifecycle-attestation block. Carries either the
  **stage-1 unauthenticated marker** (AI Chef output, before
  validation) or the **stage-3 authenticated attestation** (after
  the canonical validator stamps a passed file). Carries the file
  fingerprint, signature, issuer, validator version, timestamp and
  key id when authenticated. The block is the load-bearing trust
  signal at stage-4 consumption; the filename's `A`/`U` flag is
  decorative and is not consulted for trust (rules.md A0.7, R10).
- **Reference**: → §16, rules.md R, A0.6

---

## Section 3 — Type-prefixed deterministic ID patterns

Every entity in the v3.2 file carries a deterministic id of the form
`<typePrefix><10 hex>`. The 10 hex digits derive from the canonical
id derivation profile.

### 3.1 `$fileId`
- **Pattern**: `^f[0-9a-f]{10}$`
- **Purpose**: file id. Derived from the recipe's `name`.

### 3.2 `$ingredientId`
- **Pattern**: `^i[0-9a-f]{10}$`
- **Purpose**: ingredient id. Derived from `cookpit.ingredients[].text`
  + array index. Splits (sub-portions) also use this id type.

### 3.3 `$equipmentId`
- **Pattern**: `^e[0-9a-f]{10}$`
- **Purpose**: equipment id. Derived from `cookpit.equipment[].text` + index.

### 3.4 `$utensilId`
- **Pattern**: `^u[0-9a-f]{10}$`
- **Purpose**: utensil id.

### 3.5 `$sundryId`
- **Pattern**: `^s[0-9a-f]{10}$`
- **Purpose**: sundry id.

### 3.6 `$prereqId`
- **Pattern**: `^q[0-9a-f]{10}$`
- **Purpose**: prerequisite-item id. Used by every prereq group
  (ingredients, equipment, utensils, sundries, skills, notes) — the
  derivation distinguishes the group via `entityType`.
- **Reference**: → canonical-id-derivation.md §3.6

### 3.7 `$processId`
- **Pattern**: `^p[0-9a-f]{10}$`
- **Purpose**: process id.

### 3.8 `$taskId`
- **Pattern**: `^t[0-9a-f]{10}$`
- **Purpose**: task id. Derived from `tasks[].action` + `tasks[].time`
  (the lane-time, NOT the array index — anchors the id to the
  source-derived moment). The derivation's `entityType` is
  phase-scoped (`task` for liveCook, `prepCook-task`,
  `preCook-task`) so identical actions in different phases
  produce distinct ids.
- **Reference**: → canonical-id-derivation.md §3.9

### 3.9 `$hotspotId`
- **Pattern**: `^h[0-9a-f]{10}$`
- **Purpose**: hotspot id. Hotspots live in `cookpit.prerequisites.hotspots[]`
  and reference task ids that mark dish-critical moments.
  Hotspot `taskRefs` may target tasks in any declared phase.

### 3.10 `$prepCookId`
- **Pattern**: `^y[0-9a-f]{10}$`
- **Purpose**: prepCook phase id. Identifies `cookpit.prepCook`.
  Distinct prefix `y` separates the prepCook namespace from the
  preCook (`z…`) and file (`f…`) namespaces.
- **Reference**: → canonical-id-derivation.md §3.10

### 3.11 `$preCookId`
- **Pattern**: `^z[0-9a-f]{10}$`
- **Purpose**: preCook phase id. Identifies `cookpit.preCook`.
- **Reference**: → canonical-id-derivation.md §3.11

(liveCook does not carry a phase id of its own — its identity is
the file's `cookpit.id` (`f…`); see §3.12 of
canonical-id-derivation.md.)

---

## Section 4 — Time and duration patterns

### 4.1 `$duration`
- **Pattern**: `^[0-9]{2}:[0-5][0-9]:[0-5][0-9]$`
- **Purpose**: HH:MM:SS time-of-day or duration. Hours range
  00-99, so each phase timer accommodates up to 99h 59min 59sec.
- **Used at**: `cookpit.prepCook.nominalDuration`,
  `cookpit.preCook.nominalDuration`,
  `cookpit.liveCook.nominalDuration`.

### 4.2 `$isoDuration`
- **Pattern**: ISO 8601 duration (`P…`)
- **Purpose**: ISO 8601 durations. Used for prep/cook/total times,
  process target/min/max durations, prereq leadTime,
  temperature.atOffset.

### 4.3 `$signedIsoDuration`
- **Pattern**: ISO 8601 duration, optionally negative
- **Purpose**: signed ISO duration. Used by `timingBasis.offset`
  with `sourceImpliedDeadline` basis to record the deduced prep
  duration as a negative offset from the consumer task.
- **Example**: `"-PT2M"` for "2 minutes before the consumer task"

### 4.4 `$laneTime`
- **Pattern**: `^[0-9]{2}:[0-5][0-9]:[0-5][0-9]\.(A0|S[1-3]|M[1-3]|D[1-3])$`
- **Purpose**: lane-time stamp combining clock time and lane label.
  The seconds component MUST match the lane (A0=:00, S1=:15, S2=:20,
  S3=:25, M1=:30, M2=:35, M3=:40, D1=:45, D2=:50, D3=:55).
- **Used at**: `tasks[].time`.
- **Reference**: rules.md D5

---

## Section 5 — Lane model

### 5.1 `$lane`
- **Enum**: `["A0", "S1", "S2", "S3", "M1", "M2", "M3", "D1", "D2", "D3"]`
- **Purpose**: the 10 lane labels. A0 is the global alarm lane;
  S1-3, M1-3, D1-3 are course-scoped.

### 5.2 `$courseLane`
- **Enum**: `["S1", "S2", "S3", "M1", "M2", "M3", "D1", "D2", "D3"]`
- **Purpose**: subset of `$lane` excluding A0. Used where only
  course-scoped lanes are valid.

### 5.3 `$course`
- **Enum**: `["starter", "main", "dessert"]`
- **Purpose**: the three courses.

### 5.4 `$laneModel.type`
- **Constant**: `"fixedPrimarySecondaryTertiaryCourseLanes"`
- **Purpose**: identifies the v3.2 lane model. Always this constant.

### 5.5 `$laneModel.alarmLane`
- **Object** with: `lane: "A0"`, `second: 0`, `scope: "global"`,
  `defaultSound: "klaxon"`
- **Purpose**: the alarm-lane block. Always exactly this shape.

### 5.6 `$laneModel.courseLanes.starter / .main / .dessert`
- **Each**: array of 3 `$courseLanesArray` items
- **Purpose**: the three lanes per course, with their second
  (15/20/25 for starter; 30/35/40 for main; 45/50/55 for dessert),
  role (primary/secondary/tertiary), and defaultSound.

### 5.7 `$courseLanesArray[].lane`
- **Type**: `$courseLane`
- **Purpose**: the lane label.

### 5.8 `$courseLanesArray[].second`
- **Type**: `integer` (15-55)
- **Purpose**: the seconds-of-minute for this lane.

### 5.9 `$courseLanesArray[].role`
- **Enum**: `["primary", "secondary", "tertiary"]`

### 5.10 `$courseLanesArray[].defaultSound`
- **Type**: `$sound` (see 6.1)

### 5.11 `$sound`
- **Enum**: `["bell", "klaxon", "chime", "tick"]`
- **Purpose**: the four sound options. A0 alarm-lane defaults to
  klaxon; course lanes default to bell. Per-task override via
  `tasks[].sound`.

---

## Section 6 — Orchestration

The orchestration block declares the timing semantics — how the
live timer relates to source timing, how prep is handled, how
overruns are managed.

### 6.1 `cookpit.orchestration.mode`
- **Constant**: `"countup"`
- **Purpose**: timer mode. Always count-up from 00:00:00 in v3.2.

### 6.2 `cookpit.orchestration.timingBasis`
- **Constant**: `"cookTime"`
- **Purpose**: identifies that the sum of phase `nominalDuration`s
  is bound to the source's cook time (not total time). Per rule C2.

### 6.3 `cookpit.orchestration.prepHandling`
- **Constant**: `"preStartChecklist"`
- **Purpose**: prep is modelled as pre-Start prerequisites, not as
  timed tasks. Per rule F1.

### 6.4 `cookpit.orchestration.livePrepHandling`
- **Constant**: `"scheduledWithinCookDuration"`
- **Purpose**: prep that must happen during the live cook (per rule
  F2) is scheduled as normal timed tasks, not as a separate prep
  timer.

### 6.5 `cookpit.orchestration.startEnabledBy`
- **Constant**: `"allPrerequisitesConfirmed"`
- **Purpose**: the Start button condition. Per rule F3.

### 6.6 `cookpit.orchestration.timelineStyle`
- **Constant**: `"sequentialTimedPlan"`

### 6.7 `cookpit.orchestration.timingPolicy`
- **Constant**: `"sourceDerivedDeterministicOptimal"`
- **Purpose**: identifies the chef-detective's deductive timing
  policy. Per rules.md A1.

### 6.8 `cookpit.orchestration.overlapPolicy`
- **Constant**: `"usePassiveTimeForActiveWork"`
- **Purpose**: blesses the chef-detective's "schedule active work
  inside passive windows" pattern (sourceMeanwhile basis).

### 6.9 `cookpit.orchestration.runtimeOverruns`
- **Constant**: `"appOwned"`
- **Purpose**: declares that runtime overrun handling is the Chef
  app's responsibility, not the file's. Per rule A3.

---

## Section 7 — Prerequisites

### 7.1 `cookpit.prerequisites.ingredients[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: pre-Start ingredient prep — chopping, weighing,
  beating, blanching. Items here are confirmed before Start.

### 7.2 `cookpit.prerequisites.equipment[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: pre-Start equipment readiness — oven preheat
  (with `leadTime: PT15M`), pan on the hob, etc.

### 7.3 `cookpit.prerequisites.utensils[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: utensils to-hand prereqs.

### 7.4 `cookpit.prerequisites.sundries[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: sundries-to-hand prereqs (cling film cut, parchment
  ready).

### 7.5 `cookpit.prerequisites.skills[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: skill checks. The user confirms they're comfortable
  with the named technique before Start.

### 7.6 `cookpit.prerequisites.hotspots[]`
- **Each**: `$hotspotItem`
- **Purpose**: dish-critical moments. Each hotspot references the
  task(s) it bears on; the Chef app surfaces these at the bound
  task moment.

### 7.7 `cookpit.prerequisites.notes[]`
- **Each**: `$prerequisiteItem`
- **Purpose**: free-text notes that need user acknowledgement
  before Start. Used for chef-detective decisions, source-content
  filter decisions, range-resolution justifications.

### 7.8 `$prerequisiteItem.id`
- **Type**: `$prereqId`
- **Required**: yes

### 7.9 `$prerequisiteItem.text`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: the prereq's user-facing text. Lexicon applies fully
  for action-bearing prereqs and lightly for state descriptions
  (per lexicon.md §1 scope table).

### 7.10 `$prerequisiteItem.leadTime`
- **Type**: `$isoDuration`
- **Required**: optional
- **Purpose**: the source-implied window before the live timer can
  start. Three-tier vocabulary corpus-confirmed: `P1D` (overnight),
  `PT8H` (multi-hour press / chill), `PT15M` (oven preheat).
- **Reference**: → canonical-patterns.md §3

### 7.11 `$prerequisiteItem.ingredientRefs[]`
- **Type**: array of `$ingredientId`
- **Purpose**: structural references to the ingredients this prereq
  concerns. Closure rule applies; V-REFS-COVERAGE recognises these
  as legitimate consumers.

### 7.12 `$prerequisiteItem.equipmentRefs[]`
- **Type**: array of `$equipmentId`
- **Purpose**: structural equipment references. Same closure
  semantics.

### 7.13 `$prerequisiteItem.utensilRefs[]`
- **Type**: array of `$utensilId`

### 7.14 `$prerequisiteItem.sundryRefs[]`
- **Type**: array of `$sundryId`

### 7.15 `$prerequisiteItem.helpRef`
- **Type**: `string`
- **Purpose**: optional help-content URL or anchor for the Chef app.

### 7.16 `$hotspotItem.id`
- **Type**: `$hotspotId`

### 7.17 `$hotspotItem.text`
- **Type**: `string` (minLength 1)
- **Purpose**: the hotspot's user-facing description — typically a
  warning about what goes wrong if the moment is missed (e.g. "If
  the eggs scramble you've lost it").

### 7.18 `$hotspotItem.taskRefs[]`
- **Type**: array of `$taskId` (length ≥ 1)
- **Purpose**: the task(s) the hotspot bears on.

---

## Section 8 — Resource definitions

### 8.1 `$ingredient.id`
- **Type**: `$ingredientId`
- **Required**: yes

### 8.2 `$ingredient.text`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: the chef-detective's typed ingredient label. May
  carry purpose qualifiers ("Butter for the bechamel", "Pork stock
  for the braise") to disambiguate same-noun-different-purpose
  cases.

### 8.3 `$ingredient.quantity`
- **Type**: `$quantityValue` (number or `{min, max}`)
- **Required**: optional
- **Purpose**: the canonical quantity. Omit for `toTaste`,
  `toServe`, `asNeeded` semantic units (those carry the meaning
  in `unit` alone).

### 8.4 `$ingredient.unit`
- **Type**: `string`
- **Required**: optional
- **Purpose**: the canonical unit. Drawn from the canonical-units
  vocabulary.
- **Reference**: → canonical-units.md

### 8.5 `$ingredient.size`
- **Type**: `string`
- **Required**: optional
- **Purpose**: optional size qualifier ("large", "medium", "small")
  per source phrasing.

### 8.6 `$ingredient.metricQuantity` / `$ingredient.metricUnit`
- **Type**: `$quantityValue` / `string`
- **Required**: optional
- **Purpose**: optional metric-equivalent values when the canonical
  quantity is non-metric.

### 8.7 `$ingredient.metricApproximate`
- **Type**: `boolean`
- **Required**: optional
- **Purpose**: flags the metric value as a rounding rather than
  exact conversion.

### 8.8 `$ingredient.container`
- **Type**: `object` with `type` (string) and `quantity` (number)
- **Required**: optional
- **Purpose**: structural packaging context — `{type: "jar", quantity: 1}`
  for "1 jar", `{type: "can", quantity: 2}` for "2 cans". Distinct
  from `unit`; preferred over `unit: "jar"`.

### 8.9 `$ingredient.course`
- **Type**: `$course`
- **Required**: optional
- **Purpose**: which course this ingredient serves.

### 8.10 `$ingredient.section`
- **Type**: `string`
- **Required**: optional
- **Purpose**: within-course grouping label (`"Filling"`, `"Topping"`,
  `"Sauce"`, `"Seasoning"`). Drives Chef-app shopping-list grouping.

### 8.11 `$ingredient.optional`
- **Type**: `boolean`
- **Required**: optional, default `false`
- **Purpose**: flags decorative / "to serve" ingredients exempt
  from V-REFS-COVERAGE coverage requirements.

### 8.12 `$ingredient.alternative`
- **Type**: `object` with `text` (required), `quantity`, `unit`,
  `metricQuantity`, `metricUnit`
- **Required**: optional
- **Purpose**: source-stated PRIMARY + FALLBACK substitution
  (`"vanilla pod, or vanilla extract"`). Distinct from `choices[]`.

### 8.13 `$ingredient.choices`
- **Type**: array (≥ 2) of choice objects
- **Required**: optional
- **Purpose**: source-stated EQUIVALENT same-role options the chef
  picks from (`"cod, haddock or pollock"`; `"pearl onions or 24 baby
  onions"`). Each choice carries its own quantity+unit.

### 8.14 `$ingredient.splits`
- **Type**: array (≥ 2) of split objects
- **Required**: optional
- **Purpose**: partitioning of this ingredient across multiple tasks
  at distinct fractions (`"three-quarters for the mash, the
  remaining quarter for the topping"`). Each split has its own
  `i…` id, a fraction (0 < f ≤ 1), optional label, optional usedBy
  taskId.

#### `$ingredient.splits[].id`
- **Type**: `$ingredientId`
- **Required**: yes

#### `$ingredient.splits[].fraction`
- **Type**: `number` (0 < f ≤ 1)
- **Required**: yes

#### `$ingredient.splits[].label`
- **Type**: `string`
- **Required**: optional

#### `$ingredient.splits[].usedBy`
- **Type**: `$taskId`
- **Required**: optional

### 8.15 `$ingredient.extras`
- **Type**: array (≥ 1) of extras objects
- **Required**: optional
- **Purpose**: "plus extra for X" annotations
  (`"120g unsalted butter, plus extra for greasing"`).

#### `$ingredient.extras[].purpose`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: short phrase describing the extra's use.

#### `$ingredient.extras[].quantity` / `$ingredient.extras[].unit`
- **Type**: optional
- **Purpose**: optional explicit quantity/unit for the extra.

### 8.16 `$ingredient.depth`
- **Type**: `object` with `value`, `unit`, optional `vesselRef`
- **Required**: optional
- **Purpose**: fill-to-depth specification for ingredients added by
  depth not volume (deep-fry oil). `unit` from `["cm", "inch", "in"]`.
  `vesselRef` points at the equipment whose geometry determines
  actual volume.

---

### 8.17 `$equipment.id`
- **Type**: `$equipmentId`
- **Required**: yes

### 8.18 `$equipment.text`
- **Type**: `string` (minLength 1)
- **Required**: yes

### 8.19 `$equipment.course`
- **Type**: `$course`
- **Required**: optional

### 8.20 `$equipment.power`
- **Enum**: `["electric", "gas", "induction", "none"]`
- **Required**: optional
- **Purpose**: optional power-source qualifier. The chef-detective
  may use this to make timing decisions where the source is silent
  on heat-up time. Heat-level abstraction principle keeps this
  optional and rarely populated.

### 8.21 `$equipment.notes`
- **Type**: `string`
- **Required**: optional
- **Purpose**: free-text notes ("wok works equally", "non-stick").

### 8.22 `$equipment.choices`
- **Type**: array (≥ 2) of choice objects with `text` and `notes`
- **Required**: optional
- **Purpose**: source-stated equipment equivalents (`"deep fryer or
  deep saucepan"`).

### 8.23 `$utensil.id` / `.text` / `.course`
- **Same shape** as equipment without `power`/`notes`/`choices`.

### 8.24 `$sundry.id` / `.text` / `.course`
- **Same shape**.

---

## Section 9 — Tasks and processes

### 9.1 `$task.id`
- **Type**: `$taskId`
- **Required**: yes

### 9.2 `$task.time`
- **Type**: `$laneTime`
- **Required**: yes
- **Purpose**: the lane-time stamp. Encodes both the clock moment
  and the lane.

### 9.3 `$task.kind`
- **Enum**: `["alarm", "alert", "update"]`
- **Required**: yes
- **Purpose**: classifies the task. `alarm` is reserved for the A0
  global alarm lane (start, remaining-time warnings, time-up).
  `alert` is the standard course-lane prompt. `update` is a passive
  state notification.

### 9.4 `$task.course`
- **Type**: `$course`
- **Required**: yes for alert/update; not used for alarm
- **Purpose**: which course this task belongs to. Schema's
  conditional allOf branches enforce lane↔course consistency
  (e.g. `lane: "M2"` forces `course: "main"`).

### 9.5 `$task.lane`
- **Type**: `$lane`
- **Required**: yes for alert/update
- **Purpose**: the lane label, redundant with the seconds component
  of `time`. Schema enforces consistency.

### 9.6 `$task.action`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: the user-facing action sentence. Lexicon applies
  fully — rebel-chef voice, imperative, no UI verbs, no hedgers.
  Source typos are silently corrected here per source-content-handling
  rules.

### 9.7 `$task.sound`
- **Type**: `$sound`
- **Required**: optional
- **Purpose**: per-task sound override. When omitted, the lane's
  defaultSound applies.

### 9.8 `$task.{ingredientRefs,equipmentRefs,utensilRefs,sundryRefs,processRefs}`
- **Type**: arrays of corresponding ids
- **Required**: optional
- **Purpose**: structural references the task consumes. Closure rule
  applies — every ref must resolve to a declared entity of the
  matching type.

### 9.9 `$task.completion`
- **Type**: `$completion` (oneOf 4 shapes — see Section 11)
- **Required**: optional
- **Purpose**: optional sensory / temperature / compound completion
  cue describing what success looks like.

### 9.10 `$task.timingBasis`
- **Type**: `$timingBasis`
- **Required**: required for alert/update; not used for alarm
- **Purpose**: the audit trail justifying this task's time.

---

### 9.11 `$process.id`
- **Type**: `$processId`
- **Required**: yes

### 9.12 `$process.label`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: short present-continuous noun phrase
  (`"Boiling the spaghetti"`, `"Roasting the chicken"`). Lexicon
  §3.6 caps at 3-5 words; phase qualifiers permitted.
- **Reference**: → canonical-patterns.md §4

### 9.13 `$process.course`
- **Type**: `$course`
- **Required**: yes

### 9.14 `$process.startTask`
- **Type**: `$taskId`
- **Required**: yes
- **Purpose**: the task that begins this process.

### 9.15 `$process.endTask`
- **Type**: `$taskId`
- **Required**: yes
- **Purpose**: the task that ends this process. May reference a task
  on a different lane (cross-lane processes are conformant).

### 9.16 `$process.duration`
- **Type**: `object` with `target` (required), `min`, `max`
- **Required**: yes
- **Purpose**: ISO 8601 duration target. The interval between
  startTask.time and endTask.time (ignoring lane seconds) MUST equal
  duration.target per V-PROCESSES.

### 9.17 `$process.temperature`
- **Type**: array (≥ 1) of temperature-phase objects
- **Required**: optional
- **Purpose**: temperature schedule for ovens / hobs that change
  setting during the process.

#### `$process.temperature[].value` / `.unit`
- **Type**: `number` / enum `["C", "F"]`
- **Required**: yes

#### `$process.temperature[].fanValue` / `.gasValue`
- **Type**: optional
- **Purpose**: preserve source's `180C/160C Fan/Gas 4` notation.
  `gasValue` accepts string for fraction-mark gas marks (`"¼"`).

#### `$process.temperature[].phase`
- **Enum**: `["preheat", "cook", "rest"]`
- **Required**: yes

#### `$process.temperature[].atOffset`
- **Type**: `$isoDuration`
- **Required**: optional
- **Purpose**: offset from process startTask at which this
  temperature applies. Omit on the first phase.

### 9.18 `$process.completion`
- **Type**: `$completion`
- **Required**: yes
- **Purpose**: completion cue describing the process's success
  state.

---

## Section 10 — `$timingBasis`

The audit trail for non-alarm tasks. Every alert/update task carries
a timingBasis recording the kind of evidence used to choose the
task's time and the source phrase that justifies it.

### 10.1 `$timingBasis.basis`
- **Enum**: `["sourceExactDuration", "sourceRangeMinimum",
  "sourceRangeTarget", "sourceCookTimeEndpoint", "sourceOrder",
  "sourceMeanwhile", "sourceOutcomeCue", "sourceImpliedDeadline",
  "canonicalProcessEstimate"]`
- **Purpose**: identifies the evidence kind. See rules.md I8 for
  the full evidence-table.

### 10.2 `$timingBasis.source`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: the source phrase that justifies the chosen time.
  For `canonicalProcessEstimate`, a one-line professional-practice
  rationale.

### 10.3 `$timingBasis.offsetFrom`
- **Type**: `$taskId`
- **Required**: yes when basis is `sourceImpliedDeadline`
- **Purpose**: the consumer task whose deadline this prep task is
  deduced from.

### 10.4 `$timingBasis.offset`
- **Type**: `$signedIsoDuration`
- **Required**: yes when basis is `sourceImpliedDeadline`
- **Purpose**: the deduced prep duration as a negative offset from
  the consumer task. Always negative (`-PT2M`, `-PT5M`, etc.).

---

## Section 11 — `$completion` (4 oneOf branches)

Completion cues describe outcome state. v3.2 distinguishes four
completion types:

### 11.1 `timed`
- **Shape**: `{ type: "timed" }`
- **Purpose**: the process/task completes when its duration elapses.
  No sensory cue needed.

### 11.2 `sensory`
- **Shape**: `{ type: "sensory", modality, cue, confirm? }`
- **Modality enum**: `["visual", "aural", "tactile", "olfactory"]`
- **Purpose**: single-modality sensory completion.
- **Reference**: lexicon.md §6 sensory vocabulary

### 11.3 `temperature`
- **Shape**: `{ type: "temperature", target, unit, confirm? }`
- **Unit enum**: `["C", "F"]`
- **Purpose**: probe-driven completion (e.g. "until probe reads
  52 °C for medium-rare beef").

### 11.4 `compound`
- **Shape**: `{ type: "compound", conditions: [{modality, cue}, ...], confirm? }`
- **Purpose**: multi-modality completion. Each condition is a
  modality+cue pair; the cue passes when ALL conditions are met.

### 11.5 `confirm` (used by sensory/temperature/compound)
- **Enum**: `["user", "auto"]`
- **Required**: optional
- **Purpose**: declares whether the user confirms completion or
  the app auto-detects (e.g. via probe).

---

## Section 12 — Quantitative fingerprint and generation metadata

### 12.1 `$quantitativeFingerprint.type`
- **Constant**: `"strict"`

### 12.2 `$quantitativeFingerprint.basis`
- **Constant**: `"ingredients-and-method-active-numbers"`

### 12.3 `$quantitativeFingerprint.normalization`
- **Constant**: `"cookpit-active-number-sequence-v3.2.0"`
- **Reference**: → canonical-fingerprint-normalisation.md

### 12.4 `$quantitativeFingerprint.sequence`
- **Pattern**: `^[0-9]+(-[0-9]+)*$`
- **Purpose**: the dash-separated active-number sequence extracted
  from the source per the published normalisation.

### 12.5 `$quantitativeFingerprint.hash.algorithm`
- **Constant**: `"sha256"`

### 12.6 `$quantitativeFingerprint.hash.value`
- **Pattern**: `^[0-9a-f]{64}$`
- **Purpose**: lowercase 64-hex SHA-256 of the sequence string.

---

### 12.7 `$generation.profile`
- **Type**: `string` (minLength 1)
- **Purpose**: the canonical generation profile name. Default value
  for v3.2 files: `"cookpit-ai-canonical-v3.2"`.

### 12.8 `$generation.idPolicy`
- **Constant**: `"deterministic-type-prefixed-10-hex"`

### 12.9 `$generation.timingPolicy`
- **Constant**: `"source-derived-deterministic-optimal"`

### 12.10 `$generation.resourcePolicy`
- **Constant**: `"closed-world-declared-resources"`

### 12.11 `$generation.randomTimingAllowed`
- **Constant**: `false`

### 12.12 `$generation.taskOrdering`
- **Constant**: `"time-lane-id"`

### 12.13 `$generation.prepTiming`
- **Constant**: `"pre-start-checklist"`

### 12.14 `$generation.lanePolicy`
- **Constant**: `"fixed-primary-secondary-tertiary"`

---

## Section 13 — `phaseBlock` (the three-phase model)

The `$phaseBlock` $def is the shared shape used by `cookpit.prepCook`,
`cookpit.preCook` and `cookpit.liveCook`. Each phase is a
self-contained timed block with its own clock, lanes, tasks and
processes. The phase clock starts at `00:00:00` when the Chef app
begins the phase and runs to the phase's `nominalDuration`.

### 13.1 `$phaseBlock.id`
- **Type**: phase-specific id
- **Required**: yes for `prepCook` (`$prepCookId`, `^y[0-9a-f]{10}$`)
  and `preCook` (`$preCookId`, `^z[0-9a-f]{10}$`); ABSENT for
  `liveCook` (its identity is the file id `cookpit.id`).
- **Reference**: → canonical-id-derivation.md §3.10–§3.12

### 13.2 `$phaseBlock.label`
- **Type**: `string` (minLength 1)
- **Required**: yes
- **Purpose**: short human label for the phase, e.g. `"belly press"`,
  `"cheek braise"`, `"final assembly cook"`. Drives the
  phase-id derivation for prepCook / preCook.

### 13.3 `$phaseBlock.nominalDuration`
- **Type**: `$duration` (HH:MM:SS)
- **Required**: yes
- **Purpose**: the phase's clock duration. The Chef app's per-phase
  A0 timer runs from 00:00:00 to this value. Drives the alarm rules
  (E1-E5) for tasks within the phase.

### 13.4 `$phaseBlock.tasks`
- **Type**: `array<task>` (length ≥ 1)
- **Required**: yes
- **Purpose**: the deduced timeline within the phase. Each task has
  a lane-time, kind, action sentence and timingBasis audit trail.
  Same shape as the v3.2 task definition.
- **Reference**: rules.md I

### 13.5 `$phaseBlock.processes`
- **Type**: `array<process>`
- **Required**: optional
- **Purpose**: ongoing background activities WITHIN the phase. Each
  process's `startTask` and `endTask` must reference tasks declared
  in the SAME phase's `tasks[]` (rules.md Q5).
- **Reference**: rules.md J

### 13.6 `$phaseBlock.completion`
- **Type**: `$completion`
- **Required**: optional
- **Purpose**: sensory cue describing "phase complete" state. For
  liveCook this is the dish-on-plate cue. For preCook this is the
  cooked-component cue (e.g. "cheeks tender to a knife tip;
  meringue dry on top, soft within"). For prepCook it is the
  prep-done cue.

### 13.7 Phase composition reference

| File composition           | Source signature                                         |
| ---                        | ---                                                      |
| liveCook only              | Single-arc recipe (≤ 90 min cook); the canonical default |
| preCook + liveCook         | Cooked mainstay component before final assembly          |
| prepCook + liveCook        | Active timed prep window before cooking                  |
| prepCook + preCook + liveCook | Both — pork-fillet-braised-cheeks-and-pork-belly      |

Runtime ordering: `prepCook` ⊥ `preCook` (independent — chef may
run them concurrently or sequentially); `liveCook` ≻ both (strictly
downstream — runs only after the LAST upstream phase fires its A0
time-up). liveCook never overlaps the upstream phases. See
rules.md Q4 and canonical-patterns.md §8.6.

→ canonical-patterns.md §8 documents each composition with
active-corpus examples.

---

## Section 14 — `$recipeInstructionItem` (schema.org pass-through)

The `recipeInstructions[]` array uses one of three shapes per item:

### 14.1 Plain string
- **Purpose**: free-text method step.

### 14.2 `HowToStep` object
- **Shape**: `{ "@type": "HowToStep", "name"?, "text"?, "url"? }`
- **Purpose**: schema.org structured method step.

### 14.3 `HowToSection` object
- **Shape**: `{ "@type": "HowToSection", "name"?, "itemListElement": [...] }`
- **Purpose**: schema.org grouped method section.

---

## Section 15 — `$quantityValue`

Used wherever a number-or-range is acceptable.

### 15.1 Number form
- **Type**: `number`
- **Purpose**: a single value.

### 15.2 Range form
- **Type**: `object` with required `min` and `max` numbers
- **Purpose**: a closed range when the chef-detective wants to
  preserve range semantics structurally rather than collapsing to
  a single value.

---

## Section 16 — `attestation` lifecycle block

The `attestation` $def is a discriminated object whose shape depends on
`status`. AI Chef output (stage 1) carries the unauthenticated form;
the canonical validator's stage-3 output replaces it with the
authenticated form. The two forms share the same JSON path
(`cookpit.attestation`) but carry different field sets.

→ rules.md R for the full lifecycle contract.
→ rules.md A0.6 for the distinction between this file fingerprint and
  the source fingerprint at §12.

### 16.1 `$attestation.status`
- **Constant set**: `"unauthenticated"` | `"authenticated"`
- **Purpose**: the discriminator. Drives which sub-fields are required.
- **Reference**: rules.md R1, R2, R3

### 16.2 Unauthenticated form (stage 1)
- **Required fields**: `status: "unauthenticated"` only.
- **Forbidden fields**: `signature`, `fileFingerprint`, `issuer`,
  `keyId`, `validatorVersion`, `issuedAt`, `canonicalization`.
- **Optional**: `selfReported` sub-object (`rulesSelfChecked: bool`,
  `validatorRun: bool`, etc.).
- **Reference**: rules.md R2, R4

### 16.3 Authenticated form (stage 3)
- **Required fields**: `status: "authenticated"`, `issuer`,
  `validatorVersion`, `issuedAt`, `canonicalization`, `keyId`,
  `fileFingerprint`, `signature`.
- **Optional**: `audit` sub-object holding the validator's report
  summary (`hardFailures`, `softWarnings`, `infos`).
- **Reference**: rules.md R3, R5, R6

### 16.4 `$attestation.issuer`
- **Type**: `string` (URI)
- **Purpose**: canonical validator endpoint URL, e.g.
  `"https://cookpit.spec/v3.2/validate"`. Consumers pin this value.
- **Reference**: rules.md R3

### 16.5 `$attestation.validatorVersion`
- **Type**: `string` (minLength 1)
- **Purpose**: exact version string of the validator that issued the
  stamp. Consumers may enforce a minimum acceptable version.
- **Reference**: rules.md R3, R6

### 16.6 `$attestation.issuedAt`
- **Type**: `string` (ISO 8601 timestamp)
- **Purpose**: time of stamping.
- **Reference**: rules.md R3

### 16.7 `$attestation.canonicalization`
- **Type**: `string`
- **Default**: `"RFC8785"` (JSON Canonicalisation Scheme)
- **Purpose**: name of the canonicalisation profile used to compute
  the file fingerprint and the signed payload. Consumers MUST use the
  same profile when re-canonicalising for verification.
- **Reference**: rules.md R5

### 16.8 `$attestation.keyId`
- **Type**: `string` (minLength 1)
- **Purpose**: identifier of the public key used to sign. Lets a
  consumer pick the right key from a multi-key set published by the
  validator (e.g. across key-rotation overlap windows).
- **Reference**: rules.md R3, R6

### 16.9 `$attestation.fileFingerprint`
- **Type**: `string`
- **Pattern**: `^[0-9a-f]{64}$`
- **Purpose**: lowercase 64-hex SHA-256 of the canonicalised file body
  with `cookpit.attestation.signature` cleared (see rules.md R5). This
  is the file fingerprint introduced at stage 3 — the integrity-bound
  identifier for the exact stamped file. Consumers recompute it on
  load and reject any mismatch.
- **Reference**: rules.md R5, validation.md V-FILE-FINGERPRINT

### 16.10 `$attestation.signature`
- **Type**: `string`
- **Purpose**: cryptographic signature over the canonical bytes
  produced by R5, base64-encoded. Consumers verify with the public
  key resolved by `keyId`.
- **Reference**: rules.md R6, validation.md V-SIGNATURE

### 16.11 `$attestation.audit`
- **Type**: `object` (optional)
- **Purpose**: validator report summary, included for tamper-evident
  forensic context. The signature covers the audit object so that
  audit data is immutable post-stamp, even though it is not the
  primary trust signal.
- **Reference**: rules.md R3

### 16.12 Filename flag (decorative)

The filename's `<status>` segment is `A` when status is
`authenticated` and `U` otherwise (rules.md O1). The flag is
decorative; the load-bearing trust signal is the cryptographic
binding (`fileFingerprint` + `signature` against pinned key).
Filename / internal-status disagreement is a hard validation failure
(V-ATTESTATION-CONSISTENCY).

---

## Cross-reference index

| Topic                          | Authoritative document                         |
| ---                            | ---                                            |
| Persona & rebel-chef voice     | `bundle/v3.2/lexicon.md` §0                    |
| Detective deductive method     | `bundle/v3.2/prompt.md`                        |
| Numbered rules A0–R            | `bundle/v3.2/rules.md`                         |
| Lifecycle (4 stages)           | `bundle/v3.2/rules.md` §A0                     |
| Attestation block              | `bundle/v3.2/rules.md` §R, glossary §16        |
| Validation criteria            | `bundle/v3.2/validation.md`                    |
| Canonical id derivation        | `bundle/v3.2/canonical-id-derivation.md`       |
| Active-number-sequence rules   | `bundle/v3.2/canonical-fingerprint-normalisation.md` |
| Source-content categorisation  | `bundle/v3.2/source-content-handling.md`       |
| Unit vocabulary                | `bundle/v3.2/canonical-units.md`               |
| Concurrency / lane / leadTime patterns | `bundle/v3.2/canonical-patterns.md`     |
| Filename pattern (incl. A/U)   | `bundle/v3.2/canonical-patterns.md` §6, rules.md O |
| Executable validator           | `scripts/validate_cookpit_v3.2.py`             |
| Schema (executable contract)   | `schema/cookpit-cooking-file-v3.2.json`        |

---

## Conformance

A v3.2 file is conformant when:

1. It validates against `schema/cookpit-cooking-file-v3.2.json`
   (Draft 2020-12).
2. It satisfies every rule in `rules.md` A0 through R.
3. Its ids are derivable per `canonical-id-derivation.md`.
4. Its source quantitative fingerprint is computable per
   `canonical-fingerprint-normalisation.md`.
5. Every hard criterion in `validation.md` passes when checked by
   `scripts/validate_cookpit_v3.2.py`.

A v3.2 file is **authenticated** when, in addition to being
conformant, it has been processed through stage-3 attestation by the
canonical validator (`rules.md` R), carries the authenticated
attestation block, and verifies under V-FILE-FINGERPRINT and
V-SIGNATURE. Authenticated files use the `A` filename flag;
unauthenticated files use `U` (rules.md O1).

Soft criteria are advisory; soft warnings do not break conformance
but should drive prompt refinement, source-PDF cleanup, or schema
follow-up.

This glossary indexes every field. If a field is in the schema, it is
in this glossary.


---
