Mise · Ledger Schema Kickoff · Session Doc
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.
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.
Chef-owner, 40-seat farm-to-table, opening in 6 weeks, 2 kitchen staff, no back office.
Artisan donut + coffee shop, Park City. Already runs MarginEdge (costing) + Toast (POS) — the incumbent Mise must beat.
8-location California burger chain. Buyer is the VP of Supply Chain, not a line cook. Standardized menu, costed centrally, pushed to every store.
One-person taco truck, East LA. Cook, cashier, driver, buyer — all him. Phone only, hands always busy.
Mid-size catering company, 12-year ChefTec customer. Thousands of messy ingredients, years of invoices, yields never entered.
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.
| Kitchen | The user need, in their words | So 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 |
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 / structure | Why (which kitchens) | |
|---|---|---|
provenance — own_history / stated / corpus / reference / chef_confirmed | all five needed to know where a number came from | 5 |
swing + material — the dollar impact of each line's assumption | the raw signal the "what to ask" gate ranks on — everyone | 5 |
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 surface | 3 |
price_scope as an object — scope + geo + n_kitchens + recency + range | Maya & Alta: a market estimate is only trustworthy with its evidence attached | 2 |
| recursion block — reference-with-summary; child materiality bubbles up | Chomp's nested donut, Maya's house aioli | 2 |
blocks[] — an entry can cost $0 to change yet still must ask | Chomp's vanilla: pinned price, but blocks 3 other things | 1 |
type ∈ {fact, assumption, question, error}; errors bypass the gate | Maya's chanterelle (no price → never invent), Coastline's failures | 2 |
refs.question_id — points at the (unbuilt) ask-once store | freeze the ref shape so the empty knowledge app builds against it | 3 |
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 }
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.
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.
profile_prior — is it in or out?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.
basis axis under provenanceA 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%.
costed_for — the biggest structural callprice_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.
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).
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.
make_vs_buy; the gate weighs all-in costMake-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.
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.
diff object + bucket enumThe 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.
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.
blocks[], not just dollars. Chomp vanillaprice_scope object with vicinity evidence. Maya beet saladstated / basis:quote + a corpus cross-check. Forces the basis axis. Maya chickenfact_scope vs price_scope, costed_for, org-vs-location facts. Altayield_unknown, data_quality[], the diff object, split provenance. Coastline tenderloinThe 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.
stated / kitchen memory) — every persona; the reason F4→F5 is done by hand today. save_answer's missing table.answer_scope: org|location). Seed it from ConvMissing — 62.7K real, pre-prioritized conversion failures already in the legacy data.ledger_version: 1 · add fields freely · never rename/repurpose without both sign-offs.core/ledger.py + the 8 fixtures as contract tests.