Mise · Ledger Schema Kickoff · Session Doc

From Five Kitchens to One Contract

The ledger is the receipt every cost comes with — what Mise assumed, where each number came from, and what's worth a chef's attention. We derived its shape from five real kitchens, so every field below earns its place by serving a person. Read the kitchens first; the schema follows from them.

The one rule that shapes everything: freeze the shape of the receipt now; build the services that fill it later. A chain needs cross-location price comparison, a caterer needs a legacy diff, a new bistro needs a market-price lookup — none of those services ship in v1, but their output shape is the contract. Get the shape right once, and everything built on top never reworks.

01The five kitchens

Five operators who'd all use Mise, chosen because each stresses the receipt in a different way. Their journeys are the "why" behind every schema decision in §03–04.

Maya — the new bistro

greenfield · cold-start

Chef-owner, 40-seat farm-to-table, opening in 6 weeks, 2 kitchen staff, no back office.

wantsEvery dish on her ~30-item opening menu priced before she prints the menu, so she can catch the 3–4 margin-killers.
journeySends recipe photos + her vendor quote sheets → Mise costs all 30 at once → she sees per-plate cost, food-cost %, and which dishes bleed → adjusts portions or price before opening.
hasVendor quotes (prices she's been told). Zero purchase history — she hasn't opened.
The moment: most of her prices are "market estimate near you," not her own. Her only tenant data is a quote — not an invoice, not an answer. She needs the receipt to tell her, honestly, which numbers to trust and how much of the cost is really hers yet.

Chomp — the donut shop

established · nested batches

Artisan donut + coffee shop, Park City. Already runs MarginEdge (costing) + Toast (POS) — the incumbent Mise must beat.

wantsTo see whether Mise matches their trusted numbers and adds what MarginEdge can't (market comparison, faster capture).
journeyTheir nested book imports → the big batches cost (jam yields 11 kg, caramel 9 kg) → the finished donut costs by pulling small amounts from those batches.
hasReal invoice prices (own history) and deep recursion: a donut → 2 sub-recipes → their ingredients.
The moment: the Salted Caramel Donut pulls 2 fl oz of caramel and 65 g of shells — the shells at a 90% yield, the only real yield in the whole donut. The receipt must show the recursion honestly, and that one yield is the difference between the donut costing $0.40 (right) and $0.38 (a correct-looking miss).

Alta Burger — the 8-location chain

multi-location · price scope

8-location California burger chain. Buyer is the VP of Supply Chain, not a line cook. Standardized menu, costed centrally, pushed to every store.

wantsPer-location true cost, where each store overpays, and a fleet roll-up finance can act on.
journeyHQ defines the burger once → it costs 8 times, once per location against that store's own invoices → corporate sees all 8 vs. the standard, and who's overpaying.
hasOne org, eight locations, regional price variance (LA beef ≠ Sacramento beef).
The moment: the same burger costs $2.53 in Culver City and $2.37 in Sacramento. The receipt has to say which location it's for, whose price each line used, and that LA is overpaying $0.50/lb on beef vs. its own cheapest store — ~$14k/yr on one SKU.

Diego — the taco truck

solo · voice-first

One-person taco truck, East LA. Cook, cashier, driver, buyer — all him. Phone only, hands always busy.

wantsA trustworthy cost per taco and "does this change make money," by voice or one text glance, mid-service.
journeyTexts a photo of a recipe card → gets a cost + range back → later, hands on the griddle, asks aloud "what if I run carnitas?" → hears a spoken answer.
hasNo invoices (cash), no POS. Near-zero tolerance for questions mid-rush.
The moment: the same receipt has to become a spoken sentence, an SMS one-liner, and a screen — all from one engine output. And mid-service he'll answer zero or one question, not three. The receipt carries the facts; the door decides what to say and how much to ask.

Coastline — the migrating caterer

legacy · diff-verified

Mid-size catering company, 12-year ChefTec customer. Thousands of messy ingredients, years of invoices, yields never entered.

wantsTheir existing book to "just work" — already priced from their own history, numbers that look like the ones they've trusted for years.
journeyTheir ChefTec book imports → every recipe re-costs on the new engine → differences from their old numbers surface and get explained → cleanup becomes a worklist they pull, not a wall that blocks them.
hasDuplicate ingredients ("Tomato, Roma" vs "Roma Tomatoes"), 1.0 yields that mean "unknown," some junk rows.
The moment: a recipe costs cleanly from their own invoices and matches the old number — trust earned. But a duplicate ingredient split the price history, and a produce line's legacy yield is unknown, not zero-waste (so the cost is understated). When Mise's number differs from the old one, the receipt has to say why.

02What each kitchen needs the receipt to show

The bridge from person to schema. Each row is a real moment above; the last column is what the receipt must carry to serve it — and that column, deduped, becomes the contract.

KitchenThe user need, in their wordsSo the receipt must carry…
Maya"Is this my price or a guess? How much of this dish is really costed?"provenance shown plainly · a "quote" as its own kind of price · a "% from your own prices" trust dial · market estimates shown as ranges with how-many-kitchens-nearby
Chomp"Show me the donut's cost built up from my batches, and don't hide the yield."sub-recipe recursion (traceable, not duplicated) · yield application made visible · outlier prices flagged for review, not silently absorbed
Alta"Whose burger cost is this, and where am I overpaying?"which location it's costed for · who owns each fact (HQ recipe vs. store price) · a price-vs-comparable comparison
Diego"Just tell me — out loud — and only ask if it's worth a burned taco."facts only, no phrasing baked in (the door speaks it) · a dollar "swing" per line so the door can rank what to ask · nothing re-asked once answered
Coastline"Make my old book work, and explain anything that changed."legacy provenance · data-quality flags (unknown yield, merged duplicate, stale price) · an explained difference vs. the old cached number

03What every receipt carries (freeze these)

The fields all — or most — kitchens demanded independently. Convergence across five different operators is the strongest signal that a field belongs in the frozen core. This is the entry shape to lock in core/ledger.py.

Field / structureWhy (which kitchens)
provenanceown_history / stated / corpus / reference / chef_confirmedall five needed to know where a number came from5
swing + material — the dollar impact of each line's assumptionthe raw signal the "what to ask" gate ranks on — everyone5
yield block — factor · status(known|unknown) · applied · basis(AP|EP)Chomp's 90% shell, Coastline's unknown-vs-zero-waste, Maya's produce — the #1 silent-bug surface3
price_scope as an object — scope + geo + n_kitchens + recency + rangeMaya & Alta: a market estimate is only trustworthy with its evidence attached2
recursion block — reference-with-summary; child materiality bubbles upChomp's nested donut, Maya's house aioli2
blocks[] — an entry can cost $0 to change yet still must askChomp's vanilla: pinned price, but blocks 3 other things1
type ∈ {fact, assumption, question, error}; errors bypass the gateMaya's chanterelle (no price → never invent), Coastline's failures2
refs.question_id — points at the (unbuilt) ask-once storefreeze the ref shape so the empty knowledge app builds against it3

The entry skeleton to hand-write against — every fixture in §05 fills this shape:

// one entry per recipe line — facts + dollars only, never phrasing
{
  "line_id": "...", "raw_text": "...",
  "type": "fact | assumption | question | error",
  "resolved": { "item_id":"...", "value":"...",
                 "provenance": "own_history|stated|corpus|reference|chef_confirmed",
                 "basis": "invoice|quote|answer|market_median|reference_yield|sub_recipe" },
  "identity": { "provenance": "...", "matched_via":"...", "merged_from":[] },  // split from price
  "quantity": { "qty":0, "unit":"...", "basis":"AP|EP" },
  "yield": { "factor":1.0, "status":"known|unknown", "applied":false, "provenance":"..." },
  "price_scope": { "scope":"tenant|corpus_nearby|corpus_wide", "geo":"...",
                    "n_kitchens":0, "staleness_days":0, "range":[0,0], "outlier_flag":false },
  "fact_scope": "org|location|corpus",    // WHO owns it — orthogonal to price_scope
  "line_cost": 0.00,
  "variance": [{ "source":"identity|price|quantity_interpretation|yield|make_vs_buy", "plate_cost_delta":0 }],
  "candidates": [{ "label":"...", "plate_cost_delta":0 }],
  "swing": 0.00, "material": false, "blocks": [],
  "data_quality": [],  "sub_recipe": null,  "refs": {}, "error_code": null
}

04The judgment calls (decide in the room)

What only some kitchens forced — the keep-or-cut decisions. Each is tagged with the kitchen that surfaced it and a recommendation. These are the actual agenda.

1 · Pick the provenance vocabulary

all five flagged it

Two enums are live and disagree. Recommend the roadmap set (own_history / stated / corpus / reference / chef_confirmed) — it maps 1:1 to the price ladder. Do this first; every trust metric depends on it, and Edith emits the ledger from her next commit.

2 · profile_prior — is it in or out?

Diego vs Maya (tiebreak)

Diego (solo, no profile) called it dead; Maya (farm-to-table premium defaults) called it essential. Both are right about different layers. Drop it from the provenance enum (it never sources a value) but keep a profile-prior store as a default-selection input in the matching layer — it decides which candidate gets pre-picked, it's not where a price came from.

3 · Add a basis axis under provenance

Maya's quotes

A vendor quote is tenant-owned but unverified — neither invoice nor answer nor corpus. Recommend adding basis: invoice | quote | answer | market_median | reference_yield | sub_recipe. It's the difference between Maya's day-one trust number reading 0% or 80%.

4 · Two scope axes + costed_for — the biggest structural call

Alta the chain

price_scope (where on the ladder) and fact_scope (org / location / corpus — who owns it) are independent, plus a costed_for: location on the envelope. Freeze all three now, build the multi-location services later. Today's flat "tenant-scoped" literally can't represent a chain, and adding a scope axis after the ledger ships is exactly the rework we're avoiding.

5 · Split identity provenance from price provenance

Coastline

A ported line is identity: legacy_catalog + price: own_history — one field can't hold both. Recommend splitting; it generalizes beyond migration (any matched-then-priced line).

6 · Resolve the AP/EP yield double-count

Maya's butternut

An each-quantified whole item at <1.0 yield can get yield applied twice — a silent ±39%. Recommend: cost on as-purchased grams; the yield informs edible-portion available, it does not re-divide the cost. The receipt records quantity.basis and yield.applied so it can never happen by accident. This is the storyboard's POLICY-TBD flag — it needs a ruling, not a field.

7 · Add make_vs_buy; the gate weighs all-in cost

Maya's aioli

Make-vs-buy is a real variance the enum can't name, and its swing is labor-driven — invisible to a line-cost-only gate. Add make_vs_buy to variance_source, and the dollar gate evaluates all-in (incl. labor), not line_cost.

8 · The friction budget lives in the door, never the ledger

Diego

Diego proved the same receipt renders to voice, SMS, and screen, and that "how many questions" is dynamic (0–1 mid-service, 3 in prep). Ratify: the ledger carries swing/blocks[]; the FrictionPolicy (thresholds, context→budget) and all phrasing live in the door / knowledge app. No voice/chip/phrasing fields in the ledger, ever — that's what keeps it one contract behind many doors.

9 · Freeze the diff object + bucket enum

Coastline

The diff harness needs a recipe-level diff (the old cached cost + a synthesized staleness proxy + the delta) and a frozen bucket enum: clean · stale_cache · excluded_rows · missing_conversion · unmatched_subrecipe · legacy_incomplete · yield_difference · identity_merge · genuine_bug. Migration-specific, but it's the proof-gate contract — freeze it.

05The eight examples to write together

In the session, hand-write the ideal receipt JSON for each. Together they exercise every decision above; each traces to a kitchen. = freeze the shape, the service ships later.

1Clean own-history line — the minimum envelope. Coastline balsamic
2The nested donut — recursion, yield application, margin, child materiality bubbling up. The hardest. Chomp
3$0-swing-but-blocks — proves the gate fires on blocks[], not just dollars. Chomp vanilla
4No price → error + question — error bypasses the gate; provisional cost with bands; the question entry. Maya chanterelle
5Corpus market estimate — the price_scope object with vicinity evidence. Maya beet salad
6The quotestated / basis:quote + a corpus cross-check. Forces the basis axis. Maya chicken
7 Same recipe, two locationsfact_scope vs price_scope, costed_for, org-vs-location facts. Alta
8 Diff + data-qualityyield_unknown, data_quality[], the diff object, split provenance. Coastline tenderloin

06What we build behind the receipt

The receipt's refs point at the empty engine/knowledge app. The personas derived its first tables, in build order — the ask-once store unblocks the most, because it's why capture→verify is still concierge today.

  1. Ask-once answer store (stated / kitchen memory) — every persona; the reason F4→F5 is done by hand today. save_answer's missing table.
  2. Question queue + dollar-gate policy (the FrictionPolicy, with answer_scope: org|location). Seed it from ConvMissing — 62.7K real, pre-prioritized conversion failures already in the legacy data.
  3. Tenant conversion facts (each→weight) — the vanilla / squash / lemon answers, remembered forever.
  4. Tenant yield store — Chomp's 90% shell yield has nowhere to live today; the single likeliest silent miss.
  5. Canonical identity / merge store + review queue — Coastline's dedup, everyone's matching persistence.
  6. Later, shape-only now: StandardCost + cross-location benchmark + vicinity resolver (Alta), diff-worklist store (Coastline), profile-prior store (Maya).

The 90-minute session

  1. Ratify decision #1 (provenance enum) — 5 min, it gates everything.
  2. Walk decisions #2–9, accept/amend each recommendation — ~30 min.
  3. Hand-write the 8 fixtures against the §03 skeleton (freeze the JSON) — ~45 min.
  4. Agree the rules: ledger_version: 1 · add fields freely · never rename/repurpose without both sign-offs.
  5. Same day: Edith commits core/ledger.py + the 8 fixtures as contract tests.