Domain · Architecture

Annapurna Domain — Architecture

Every feature package follows an identical skeleton: an index.ts that exports a self-describing capability registry (ANNAPURNA_<PKG>_CAPABILITIES plus

8sections12 minread1diagrams

On this page

Architectural overview of Annapurna, the restaurant / culinary-operations / autonomous-delivery domain: the implemented @annapurna/* TypeScript library workspace, its shared core foundation, the per-pillar engine packages, the cross-domain contract and integration layers, and an honest map of what is built versus what is still planned.


Annapurna is the Oshun domain for commercial food-service operations — menu engineering, culinary intelligence, kitchen automation, front- and back-of-house workflow, procurement and food safety, autonomous and human delivery, ghost kitchens, multi-unit chains, commissaries, workforce, restaurant finance, customer intelligence, and sustainability. It is the operating system for a business that sells food, deliberately distinct from Hestia, which owns home cooking; the two meet at the point of sale.

The domain takes its name from Annapurna (अन्नपूर्णा), the Hindu goddess of food and complete nourishment — chosen because the platform aims to close the whole commercial food loop, from a supplier lot through a robot-assembled dish to a guest at a table or a thermally-controlled delivery handoff.

Status correction (important). The sibling features.md and specifications.md pages still describe Annapurna as "planned-only, no packages exist." That is no longer true and those pages are stale. The workspace is implemented: libs/annapurna/domain.config.json records "status": "foundation-and-core-implemented", and the tree below contains twenty-one @annapurna/* libraries plus a @contracts/annapurna package, all real TypeScript with tests. Where the older pages and the shipped code disagree on specifics (order statuses, MenuItem shape, an IngredientLot lifecycle), this page follows the code and flags the divergence.

A new engineer should hold three facts in mind:

  1. Annapurna is a pure-TypeScript domain library workspace, not a running service. Every package exports deterministic, side-effect-free domain functions over shared types. There is no apps/annapurna/ or services/annapurna/ directory; there is no deployed runtime yet.
  2. One package — @annapurna/core — owns every shared type, ID, Zod schema, the order state machine, the persistence schema, and the wire-contract surface. Every other package depends on it and (almost) nothing else.
  3. The polyglot stack the specification imagines (Rust robotics control, ROS2 navigation, Python ML, C/C++ firmware, live Kafka/WebSocket, React Native apps) is not present. What exists is the deterministic decision logic those runtimes would eventually wrap.

Workspace Shape#

text
libs/annapurna/
├── domain.config.json          ← { phase: 68, scopeTag: "scope:annapurna",
│                                    status: "foundation-and-core-implemented" }
├── core/                        ← @annapurna/core — shared foundation
│   ├── src/ids.ts               ← branded IDs + prefix-validated factories
│   ├── src/types.ts             ← entities, enums, COOKING_TECHNIQUES, order FSM
│   ├── src/schemas.ts           ← Zod schemas mirroring every entity
│   ├── src/taxonomy.ts          ← ANNAPURNA_CUISINE_TAXONOMY (250+ cuisines)
│   ├── src/db-schema.ts         ← table/index metadata + Redis cache strategy
│   ├── src/api.ts               ← REST/GraphQL/gRPC/WS/Kafka/OpenAPI surface
│   └── drizzle/0001_annapurna_foundation.sql  ← real DDL migration
│
├── culinary/  menu/             ← Culinary Intelligence pillar
├── restaurant/  kitchen/  foh/  boh/           ← Design & Operations pillar
├── robotics/  ai/               ← Automation & Robotics pillar
├── delivery/                    ← Autonomous Delivery pillar
├── chain/  ghost/  commissary/  ← Multi-Unit Intelligence pillar
├── supply/  safety/             ← Supply Chain & Food Safety pillar
├── staff/  finance/  customer/  sustainability/  ← cross-cutting operations
├── apps/                        ← view-model state builders for end-user apps
└── integration/                 ← cross-domain adapter layer

libs/contracts/annapurna/        ← @contracts/annapurna — wire contracts

Every feature package follows an identical skeleton: an index.ts that exports a self-describing capability registry (ANNAPURNA_<PKG>_CAPABILITIES plus list…Capabilities() / has…Capability()), a types.ts of domain models, an engines.ts of pure functions, and a colocated Vitest suite. The capability registry is the package's machine-readable manifest of what it claims to do and which source files back each claim.


Design Principles#

The five rules below are how the workspace is actually built, observed across all twenty-one packages.

  1. One foundation, many leaves. @annapurna/core is the single source of truth for entity types, branded IDs, enums, and the order state machine. Feature packages import from @annapurna/core and generally not from each other — they are independently buildable leaves over a shared trunk. @annapurna/integration is the only package that fans out, and it fans out to other domains, not to Annapurna siblings.
  2. Pure functions, no hidden state. Engines take inputs and return results. There is no database client, no network call, no clock dependency, no global mutable state inside the domain logic. Persistence and transport are described as data (db-schema.ts, api.ts) but not wired.
  3. Branded identifiers everywhere. Entity references are nominal brands (Brand<string, 'OrderId'>) minted only through prefix-validating factories (createOrderId requires an ord_ prefix), so a MenuItemId can never be passed where an OrderId is expected.
  4. Schema and type parity. Every entity in types.ts has a matching Zod schema in schemas.ts. Validation at the boundary and compile-time typing inside the domain stay in lockstep.
  5. Domain-specific math, not CRUD. The engines encode real food-service formulas — non-linear recipe scaling, the menu-engineering matrix, HACCP critical limits, FEFO inventory, prime-cost and contribution-margin accounting — rather than generic record shuffling.

Component & Data Flow#

flowchart TB subgraph Foundation CORE["@annapurna/core\nids · types · schemas · taxonomy\norder state machine · db-schema · api"] CONTRACTS["@contracts/annapurna\nevents · api-schemas · graphql\ngrpc · openapi · integration"] end subgraph Culinary["Culinary Intelligence"] CUL["@annapurna/culinary"] MENU["@annapurna/menu"] end subgraph Ops["Design & Operations"] REST["@annapurna/restaurant\n(+ kitchen design)"] FOH["@annapurna/foh"] SUP["@annapurna/supply"] STAFF["@annapurna/staff"] FIN["@annapurna/finance"] CUST["@annapurna/customer"] CHAIN["@annapurna/chain"] GHOST["@annapurna/ghost"] COMM["@annapurna/commissary"] KIT["@annapurna/kitchen*"] BOH["@annapurna/boh*"] end subgraph Automation ROB["@annapurna/robotics"] AI["@annapurna/ai"] end subgraph SafetyDelivery SAFE["@annapurna/safety"] DEL["@annapurna/delivery"] SUS["@annapurna/sustainability"] end subgraph Surface APPS["@annapurna/apps\n(view-model state)"] INT["@annapurna/integration\n(adapters)"] end CUL & MENU & REST & FOH & SUP & STAFF & FIN & CUST --> CORE CHAIN & GHOST & COMM & ROB & AI & SAFE & DEL & SUS & APPS & INT --> CORE KIT & BOH --> CORE CONTRACTS -. mirrors .-> CORE INT --> HESTIA["@hestia/*"] INT --> ASASE["@asase/*"] INT --> BRIGID["@brigid/*"] INT --> OYA["@oya/*"] INT --> MAAT["@maat/*"] INT --> SESHAT["@seshat/* · @athena/* · @freya/* · @psyche/*"] CORE -. described, not wired .-> PG[("Postgres\nPostGIS · pgvector · TimescaleDB")] CORE -. described, not wired .-> REDIS[("Redis cache")] CORE -. described, not wired .-> KAFKA{{"Kafka topics"}} %% * kitchen/boh are thin capability registries; kitchen design lives in restaurant

Control flows top-down: a hypothetical orchestrating service (not yet built) would validate input with a @contracts/annapurna Zod schema, call the relevant engine functions, advance an Order through the core state machine, and emit a @contracts/annapurna domain event. Data flows through the shared core entities the whole way.


The Foundation: @annapurna/core#

core is the package every architectural decision routes through.

Branded IDs (libs/annapurna/core/src/ids.ts). Ten entity ID brands — RestaurantId, MenuItemId, OrderId, VehicleId, TableId, StaffId, RecipeId, IngredientId, SupplierId, CustomerId — each minted by a factory that trims input and enforces a prefix (rest_, menu_, ord_, veh_, tbl_, stf_, rcp_, ing_, sup_, cus_). An empty or wrong-prefix value throws.

Entities and enums (libs/annapurna/core/src/types.ts, ~1,000 lines). Restaurant, KitchenStation, MenuItem, Order, DeliveryVehicle, Table, Reservation, StaffMember, Recipe, Ingredient, FlavorProfile, CuisineType, DietaryRequirement, plus value objects (Money over a fixed currency set including GHS/USD/EUR/NGN/KES/ZAR, NutritionalData, ThermalCompartment, NavigationCapability). Enums fix the operational vocabulary: RestaurantConcept (fine dining → ghost kitchen → food truck), KitchenStationName (grill, saute, fry, pastry, wok, sushi…), StaffRole, VehicleClass (sidewalk robot, road robot, delivery drone, bicycle…), OrderChannel, and OrderFulfillmentStatus. The file also defines COOKING_TECHNIQUES — a large catalog generated by crossing a base technique list (grill, sous*vide, ferment, spherify…) with heat-method prefixes (classic*, low*temp*, high*heat*, pressure\_), yielding the several-hundred technique union the recipe model references.

Order state machine (same file). ORDER_STATUS_TRANSITIONS is the single authority for order lifecycle: placed → confirmed → preparing → ready → picked_up → delivered, with cancelled reachable from placed/confirmed/ preparing/ready but not from picked_up or delivered (those are terminal). transitionOrder(order, next, atIso) throws on any illegal edge and stamps the new status timestamp into OrderTimestamps; canTransitionOrder is the pure predicate behind it. This is the one runtime invariant the code actually enforces.

Divergence note: the stale specifications.md describes a different machine (created → … → served) and channels (takeaway, catering, ghost_kitchen). The shipped OrderChannel is dine_in | takeout | delivery | platform and the status set is the seven values above. It also describes an IngredientLot.traceabilityStatus lifecycle and a MenuItem.grossMarginPercent field that do not exist in core; the real MenuItem carries priceTiers: Record<OrderChannel, Money>, dietaryTags, nutrition, and a recipeId, and lot traceability is modelled in the safety/supply packages and the annapurna_inventory_lots table rather than as a core enum.

Schemas (libs/annapurna/core/src/schemas.ts). A Zod schema for every entity, including ID schemas that re-assert the prefix rule, MoneySchema, NutritionalDataSchema, FlavorProfileSchema (each taste axis clamped to [0,1]), and OrderSchema with a per-status timestamp object. These are the validation boundary for any service that later wraps the domain.

Cuisine taxonomy (libs/annapurna/core/src/taxonomy.ts, ~6,500 lines). ANNAPURNA_CUISINE_TAXONOMY is a large structured catalog of CuisineType records (250+ regional cuisines spanning Africa, Europe, Asia, the Americas), each carrying signature dishes, core techniques, staple ingredients, and a FlavorProfile. It is the reference data the culinary engine draws on for concept generation and flavor reasoning.

Persistence description (libs/annapurna/core/src/db-schema.ts + drizzle/0001_annapurna_foundation.sql). Twelve tables — annapurna_restaurants, annapurna_menus, annapurna_recipes, annapurna_orders, annapurna_inventory_lots, annapurna_kitchen_equipment, annapurna_delivery_vehicles, annapurna_staff, annapurna_customers, annapurna_suppliers, and two TimescaleDB hypertables (annapurna_kitchen_iot_telemetry, annapurna_delivery_vehicle_telemetry). The real SQL migration enables postgis, timescaledb, and vector; restaurants and vehicles carry geography(point,4326) columns with GiST indexes; recipes and customers carry vector(1536) embedding columns with IVFFlat cosine indexes. ANNAPURNA_REDIS_CACHE_STRATEGY defines TTL'd key patterns for order state, KDS display, delivery tracking, and menu availability. This is a faithful schema; it is metadata and DDL, not a running database or repository layer.

Wire surface (libs/annapurna/core/src/api.ts). REST endpoints with operation IDs, a GraphQL SDL fragment, WebSocket channel names (annapurna.kds.…, annapurna.customer.order_status, annapurna.delivery.vehicle_position), a gRPC proto (KitchenRobotics, DeliveryDispatch, InventoryUpdates), Kafka topic names, a generated OpenAPI 3.1 document, Prometheus metric names, trace attributes, and auth roles. @contracts/annapurna (libs/contracts/annapurna/src/events.ts and siblings) re-states the cross-domain contract — notably an AnnapurnaDomainEventSchema with eight typed event types (annapurna.order.placed, annapurna.food.ready, annapurna.delivery.delivered, annapurna.inventory.low, …) keyed by aggregate type. Again: a described surface, not a mounted server.


The Pillar Engines#

Each engine package turns the shared core types into food-service decisions. Highlights, by pillar:

Culinary Intelligence — culinary, menu. culinary (libs/annapurna/culinary/src/engines.ts, ~1,300 lines) is the deepest engine: scaleRecipe applies non-linear scaling exponents by ingredient class (seasoning 0.82, leavening 0.70, garnish 0.90, sauces 0.95) rather than scaling everything linearly; predictFlavorPairings scores ingredient pairs on shared aroma chemistry; optimizeMaillardReaction, planFermentation, and planMolecularTechnique emit technique-specific parameter envelopes; buildAllergenMatrix propagates allergens through ingredient → recipe → menu-item and tags EU-14 and FDA-Big-9 sets; calculateRecipeNutrition rolls up per-portion nutrition with cooking-loss adjustments. menu implements the menu-engineering matrix (classifyMenuEngineering → star / plowhorse / puzzle / dog by popularity × contribution margin), psychological and anchor pricing (recommendMenuPrice), demand/daypart calculateDynamicPrice, and trackFoodCostVariance (theoretical vs. actual food cost).

Design & Operations — restaurant, foh, plus kitchen/boh. restaurant (libs/annapurna/restaurant/src/engines.ts, ~710 lines) covers both front-of-house spatial design (concept, dining layout, biophilic, lighting, acoustics, bar, outdoor) and the full commercial-kitchen design suite (planCommercialKitchenLayout, specifyKitchenEquipment, designKitchenVentilation, specifyRefrigeration, specifyPlumbing, planElectricalLoad, scheduleEquipmentMaintenance). foh runs POS sessions, KDS ticket routing, multi-channel order aggregation, kitchen-capacity throttling, reservations, waitlist, table-turn optimization, loyalty, and feedback.

Automation — robotics, ai. robotics (libs/annapurna/robotics/src/engines.ts, ~690 lines) compiles dishes into station controller programs (wok, grill/plancha, fryer, pizza line, sushi, salad, beverage, pastry) as timed action + motion-profile sequences, synchronizes a multi-robot table to a single ready time via bottleneck analysis, plans sanitization cycles against ATP-RLU thresholds, enforces dispense tolerances (±1 g spices, ±5 g proteins), and runs IoT/energy/air-quality monitoring plus plate-QC and cooking-completion vision fusion. ai (libs/annapurna/ai/src/engines.ts, ~870 lines) provides deterministic heuristic models — demand forecasting, plate-quality scoring, customer recommendations, dynamic-price optimization, kitchen-efficiency, review sentiment, route optimization, trend prediction. These are CPU heuristics in TypeScript, not trained ML models.

Supply Chain & Food Safety — supply, safety. supply implements supplier sourcing, par-level purchasing, receiving inspection, perpetual inventory, enforceFefo (first-expiry-first-out), shelf-life management, and food-cost accounting. safety (libs/annapurna/safety/src/engines.ts) builds HACCP plans grounded in Codex Alimentarius / FDA Food Code, identifying critical control points with explicit critical limits (e.g. cool 57 °C → 21 °C within 2 h, → 5 °C within 6 h; hot-hold ≥ 57 °C), temperature-deviation alerting, inspection- readiness scoring, allergen-management protocols, ATP-validated cleaning, and buildRecallResponsePlan, which resolves a recall notice to affected lots and the exact servedOrderIds — the one place lot→order traceability is concretely realized.

Multi-Unit, Finance, Customer, Sustainability. chain, ghost, and commissary handle franchise/portfolio, virtual-brand, and central-kitchen operations; finance produces P&L, prime-cost, break-even, cash-flow, and delivery-channel profitability; customer runs RFM segmentation, CLV, and churn prediction; staff does demand-based scheduling and labor analytics; sustainability tracks waste, carbon, sourcing, energy, and water.

Surface — apps, integration. apps builds view-model state objects (customer mobile state, order-tracking, KDS tablet, fleet dashboard, courier app, robot teleoperation) — the data an app would render, not the React Native app itself. integration (libs/annapurna/integration/src/engines.ts) is the cross-domain adapter layer described below.


Cross-Domain Integration#

@annapurna/integration is the only Annapurna package that reaches outside the domain, and it does so through structural adapters, not live calls. Each adapter (adaptHestiaRecipeIntelligence, buildAsaseFarmToRestaurantSupplyChain, adaptBrigidKitchenAutomation, adaptOyaAerialDelivery, adaptSeshatRestaurantDesign, adaptMaatBusinessOperations, adaptPsycheCustomerInteraction, and adapters for Athena furniture and Freya textiles) takes a typed source payload and returns an AdapterIntegrationResult naming the source/target packages, the handoff artifacts, the risk controls, and a status of blocked / ready / needs_review. This encodes the ownership boundaries the domain documents promise:

  • Hestia owns home cooking; Annapurna imports recipe/nutrition intelligence and exports meal-kit SKUs back. The boundary is the point of sale.
  • Asase owns farm-level traceability; Annapurna picks the chain up at the receiving dock (@annapurna/supply / @annapurna/safety).
  • Brigid owns industrial equipment, refrigeration, and energy; Annapurna adds the food-service-specific control and safety layer.
  • Oya owns aerial/ground autonomous vehicles; Annapurna consumes flight and fleet capability for delivery.
  • Maat consolidates cross-domain finance, compliance, and workforce; @annapurna/finance and @annapurna/staff feed it.
  • Seshat / Athena / Freya / Psyche supply spatial harmony, furniture, textiles, and conversational/voice surfaces.

Because the adapters are pure functions returning a status, they are the natural seam where a future service would attach real transport — and they fail honestly (returning blocked/needs_review) rather than pretending an integration is live.


Invariants, Failure Modes, and Extension Points#

  • Enforced invariant: illegal order transitions throw in transitionOrder. This is the only hard runtime guard in the domain today.
  • Encoded-but-unenforced intentions: the four "platform hard requirements" the older docs list (immutable safety records, lot-to-order traceability, versioned menu pricing, robotic safety-stop / incident log) are partially represented — recall traceability exists in safety, the inventory-lot table exists in db-schema — but there is no persistence layer enforcing immutability or append-only logs, and no robotic safety-stop state machine in robotics. Safety interlocks appear only as gRPC fields and integration risk controls. Treat these as design targets, not guarantees.
  • Failure mode to watch: because engines are stateless, two packages can compute inconsistent views if a caller feeds them divergent snapshots. The intended mitigation is a single orchestrating service reading from the core schema — which does not exist yet.
  • Thin packages: @annapurna/kitchen and @annapurna/boh are capability registries only — they export the manifest interface and list/has helpers but no engine logic. Kitchen-design behavior actually lives in @annapurna/restaurant; back-of-house behavior is distributed across culinary (prep/batch), robotics (cooking/cleaning), and supply (inventory). Do not look for substantive code in those two packages.
  • Extension points: add a new capability by extending an engine and appending to its ANNAPURNA_<PKG>_CAPABILITIES registry; add a new entity by defining it in core/src/types.ts, mirroring the Zod schema in schemas.ts, and (if persisted) adding a table to db-schema.ts and the Drizzle migration; add a new external dependency by writing an AdapterIntegrationResult-returning function in integration.

Honest Status Summary#

Implemented and tested (TypeScript, pure-function): @annapurna/core (IDs, entities, enums, cooking-technique catalog, order state machine, Zod schemas, cuisine taxonomy, DB schema metadata + real Drizzle SQL, wire-contract surface); @contracts/annapurna; and eighteen feature engines — culinary, menu, restaurant, foh, robotics, ai, supply, safety, staff, finance, customer, chain, ghost, commissary, sustainability, delivery, apps, integration — each with substantive, domain-specific logic and colocated tests.

Thin / placeholder: @annapurna/kitchen and @annapurna/boh (capability manifests only).

Described but not wired: persistence (no running Postgres/Timescale/pgvector or repository layer behind the schema), Redis caching, Kafka/WebSocket transport, the REST/GraphQL/gRPC servers (only the contract surface exists), and the enforced platform invariants.

Planned, not present: the polyglot runtime — Rust kitchen-robotics and delivery-navigation control, ROS2 autonomy, Python ML models, C/C++ embedded firmware — and shippable React Native applications (the apps package builds view-model state, not the apps). There are no apps/annapurna/ or services/annapurna/ projects.

In short: Annapurna today is a complete, code-grounded domain-logic and contract layer for commercial food service — the deterministic brain a future set of services and device runtimes would orchestrate — and it should be read as exactly that, not as a deployed platform.