application

Ration — System Design

Product: AI-powered kitchen management (pantry → recipes → meal plan → shopping → restock)
Platform: Cloudflare Workers (edge SSR) · Domain: ration.mayutic.com
AI surfaces: Copilot copilot.ration.mayutic.com · MCP mcp.ration.mayutic.com
Clients: Web (React Router v7) · Native iOS (SwiftUI)

This wiki is the system design homepage for project members. Deep runbooks, workflow diagrams, and operator checklists live in the private repository (README.md, docs/, ios/README.md).


1. Mission & product loop

Ration is an orbital supply-chain pantry system: zero-latency inventory, AI-assisted meal logistics, and automated waste reduction.

flowchart LR
    Cargo["Cargo<br/>Inventory"] --> Galley["Galley<br/>Recipes"]
    Galley --> Manifest["Manifest<br/>Meal plan"]
    Manifest --> Supply["Supply<br/>Shopping list"]
    Supply --> Dock["Dock<br/>Restock cargo"]
    Dock --> Cargo
Domain Responsibility
Cargo Org-scoped inventory (qty, unit, domain, expiry, tags)
Galley Recipes & provisions; cook deducts cargo; active selection feeds Supply
Manifest Calendar meal plan (breakfast / lunch / dinner / snack)
Supply Shopping list rebuilt from Galley + Manifest horizon; dock → Cargo
Hub Customizable dashboard widgets (shared layout web ↔️ iOS)
Ask Ration First-party Copilot (WebSocket + Durable Object)
MCP External agents (Claude, Cursor, ChatGPT, …) operate the same kitchen data

2. High-level architecture

Three Workers share D1 / KV / R2 / AI / Vectorize where appropriate. There are no always-on VMs — capacity scales with Cloudflare’s edge.

flowchart LR
    subgraph Clients["Clients"]
        Browser["Browser / PWA"]
        iOS["iOS SwiftUI"]
        Agent["MCP client"]
    end

    subgraph DNS["Cloudflare DNS"]
        App["ration.mayutic.com"]
        McpHost["mcp.ration.mayutic.com"]
        CopilotHost["copilot.ration.mayutic.com"]
    end

    subgraph Workers["Workers"]
        Main["ration<br/>SSR + APIs + queues"]
        Mcp["ration-mcp<br/>MCP tools"]
        Copilot["ration-copilot<br/>Ask + Think DO"]
    end

    Browser --> App --> Main
    iOS -->|Bearer /api/mobile/v1| App
    iOS -->|WSS| CopilotHost
    Browser -->|WSS| CopilotHost
    Agent -->|OAuth / API key| McpHost --> Mcp
    CopilotHost --> Copilot

2.1 Worker roles

Worker Entry Role
ration workers/app.ts React Router SSR, Better Auth, Stripe/RevenueCat webhooks, AI queue producers/consumers, REST + mobile APIs
ration-mcp workers/mcp.ts Model Context Protocol tool server; OAuth 2.1 + rtn_live_* API keys
ration-copilot workers/copilot.ts Ask Ration WebSocket; ProjectThinkAgent Durable Object; shared MCP tool runtime + docs search

2.2 Cloudflare bindings

Binding Service Purpose
DB D1 Users, orgs, cargo, meals, plans, ledger, API keys
RATION_KV KV Rate limits, embedding cache, tier cache, webhook idempotency, Copilot session state
STORAGE R2 Scan images, avatars, exports
AI Workers AI Embeddings (embeddinggemma-300m); Copilot inference (gpt-oss-120b)
VECTORIZE Vectorize (ration-cargo) Semantic ingredient match — namespaced per org
AI Gateway External Gemini (gemini-3.5-flash) for scan / generate / plan-week / import-url
Queues scan, meal-generate, plan-week, import-url Long AI jobs off the request path
PROJECT_THINK Durable Object One Think isolate per {org}:{user}:{tier}:{conversationId}
FLAGS Flagship Gradual rollout / kill switches (server-enforced)

Smart Placement: Isolates relocate near the D1 primary so DB-heavy work stays ~5ms to D1 instead of cross-ocean round-trips.


3. Request & tenancy model

3.1 Multi-tenant isolation

All kitchen data is owned by an organization (household / group), not a lone user row.

  • Session carries activeOrganizationId.
  • Every D1 query is scoped with organization_id from verified session membership (requireActiveGroup() / mobile JWT org claim) — never from unchecked client input.
  • Vectorize uses namespace = organizationId for the same isolation at the embedding layer.
  • Credits and capacity limits follow the org owner’s tier.

3.2 Auth surfaces

Surface Mechanism
Web hub Better Auth session cookies — magic link (POST continue), Google OAuth, optional Apple (Flagship-gated)
iOS PKCE magic link + Sign in with Apple / Google → mobile access JWT + refresh
REST v1 Org API keys rtn_live_* (SHA-256 hashed at rest)
MCP OAuth 2.1 (mcp:* scopes) or API key
Copilot Web: short-lived handshake token; iOS: mobile Bearer on WSS upgrade

3.3 Defence in depth (summary)

  1. Edge / TLS — CDN, HSTS, CSP, frame deny
  2. Authentication — Session / JWT / API key / OAuth
  3. Authorization — Org membership, roles, tier gates, Vectorize namespace
  4. Rate limiting — KV sliding windows; AI spend buckets fail closed
  5. Integrity — Zod at API boundary, db.batch() atomic writes, credit overdraft SQL guard, Stripe/RC webhook idempotency

4. Web application structure

Stack: React Router v7 (Framework Mode) · TypeScript · Tailwind · Drizzle ORM · Bun · Biome · Vitest / Playwright

application/
├── workers/           # Worker entries: app.ts, mcp.ts, copilot.ts
├── app/
│   ├── routes/        # File-based routes (routes.ts)
│   │   ├── hub/       # Authenticated product UI
│   │   └── api/       # Webhooks, scan, checkout, mobile, v1, …
│   ├── components/    # Feature UI (cargo, galley, manifest, supply, hub, support, …)
│   ├── lib/           # Server utilities (*.server.ts), schemas, MCP, Copilot, mobile
│   ├── db/            # Drizzle schema
│   └── test/          # Fixtures & mock Cloudflare bindings
├── drizzle/           # Generated migrations (never hand-write)
├── docs/              # Fin knowledge + developer notes
├── e2e/               # Playwright journeys
├── content/           # Blog / help markdown
└── public/            # PWA manifest + shell service worker

4.1 Route map (conceptual)

Area Paths
Marketing / legal /, /about, /blog, /help, /legal/*, /pricing (hub)
Auth /auth/*, Better Auth under /api/auth/*
Product hub /hub, /hub/cargo, /hub/galley, /hub/manifest, /hub/supply, /hub/settings
Mobile API /api/mobile/v1/*
Public REST /api/v1/{inventory,galley,supply}/…
Agent discovery /.well-known/*, /auth.md, OpenAPI JSON

4.2 Server conventions

  • *.server.ts — Cloudflare / D1 / secrets only on the server
  • Zod in app/lib/schemas/ — validate at the API boundary
  • D1 bind limit — max 100 parameters per statement; chunk with query-utils.server.ts constants
  • Writes — prefer db.batch([...]) over sequential awaits
  • AI jobs — enqueue → poll queue_job; credits deducted at gate, refunded on consumer failure
  • Feature flagsisFeatureEnabled() server-side; UI flags are insufficient for auth/AI kill switches

4.3 Async AI pipeline

flowchart LR
    Client -->|"withCreditGate"| Producer["Worker producer"]
    Producer --> Queue["Cloudflare Queue"]
    Producer --> Status["queue_job pending"]
    Queue --> Consumer["Queue consumer"]
    Consumer --> Gateway["AI Gateway → Gemini"]
    Consumer --> D1["D1 + Vectorize"]
    Client -->|"poll status"| Status
Job Typical credit cost Notes
Receipt scan 2 Vision; R2 temp image; PDF or image
Meal generate 2 Vectorize verification against pantry
Plan week 3 Fills Manifest
Import URL 1 Optional Browser Rendering for JS-heavy pages

5. iOS application structure

Stack: SwiftUI · iOS 18+ · XcodeGen (project.yml) · RevenueCat · Textual (markdown) · XCTest

Native client speaks only to /api/mobile/v1/* (Bearer JWT). Entitlements come from RevenueCat; the app reads user.tier / credits from the server.

ios/
├── project.yml              # Source of truth for Xcode project
├── ci_scripts/              # Xcode Cloud post-clone (XcodeGen + SPM pin)
├── swiftpm/Package.resolved # Pinned SPM graph for CI
├── Ration/
│   ├── App/                 # @main, DI, auth-gated root, tab shell
│   ├── Core/
│   │   ├── Auth/            # AuthManager, Keychain, PKCE
│   │   ├── Networking/      # APIClient, RationAPI, AIJobPoller
│   │   ├── Session/         # Org context, credits, AI consent
│   │   ├── Consent/         # Shared gate for scan / generate / import / plan-week
│   │   ├── Persistence/     # Org-scoped offline SnapshotStore
│   │   ├── Billing/         # RevenueCat boundary
│   │   ├── Design/          # Orbital Luxury theme, Space Mono, list chrome
│   │   └── Models/          # Codable ↔ mobile API
│   ├── Features/
│   │   ├── Dashboard/Hub/   # Widget grid (shared hubLayout with web)
│   │   ├── Cargo/ Scan/ Galley/ Manifest/ Supply/
│   │   ├── Ask/             # Copilot WebSocket UI
│   │   ├── Settings/        # Account + group + tags
│   │   ├── Billing/         # Paywall
│   │   └── Auth/ Onboarding/
│   └── Resources/           # Assets, fonts
└── RationTests/             # XCTest

5.1 Tabs & chrome

  • Tabs: Hub · Cargo · Galley · Manifest · Supply
  • Leading: Org switcher (avatar, credits, Crew pill) → Group Settings
  • Trailing: Page filters + profile → Account Settings
  • FAB: Context menus per surface; Ask Ration dock

5.2 Client architecture notes

Concern Approach
Auth PKCE magic link + Apple/Google social; Universal Links primary, ration:// fallback
Org switch New org-scoped token pair; wipe/reload via orgGeneration
Offline Org-scoped snapshots; forced logout runs full wipe (snapshots, billing, images, session)
AI consent Single SessionStore flag + AIConsentCoordinator for all four AI entry points
Async AI Same queue job IDs as web; AIJobPoller
Billing RevenueCat SDK purchases; server materializes tier/credits from webhooks when fulfillment is enabled

Versioning (iOS): MARKETING_VERSION + CURRENT_PROJECT_VERSION in project.yml (independent of web package.json, same patch/minor cadence). After project.yml edits: bun run ios:generate.

CI: Pushes to mainXcode Cloud (not .gitlab-ci.yml) → Archive → TestFlight Internal. Ration.xcodeproj is generated, not committed.


6. Data model (conceptual)

erDiagram
    user ||--o{ member : joins
    organization ||--o{ member : has
    organization ||--o{ cargo : owns
    organization ||--o{ meal : owns
    organization ||--o{ supply_list : owns
    organization ||--o{ meal_plan : owns
    organization ||--o{ ledger : credits
    meal ||--o{ meal_ingredient : contains
    meal_plan ||--o{ meal_plan_entry : schedules
    supply_list ||--o{ supply_item : lists
    tag ||--o{ cargo_tag : labels
    tag ||--o{ meal_tag : labels

Principles:

  • Organization is the tenancy root (shared pantry).
  • Tags are an org-wide registry (tag + join tables).
  • Quantity 0 is a restock reminder; delete is permanent jettison.
  • Migrations: edit app/db/schema.tsbun run db:generate → review → migrate. Never hand-write drizzle/*.sql.

7. AI & matching

Layer Implementation
Embeddings Workers AI @cf/google/embeddinggemma-300m (768-dim); KV cache ~7 days
Semantic index Vectorize ration-cargo, cosine, org namespace
Match thresholds ~0.78 product paths; ~0.60 MCP search recall
Meal match Strict (100% cookable) vs delta (partial); unit conversion + density
LLM (ops) AI Gateway → Gemini for vision/text jobs
Copilot Workers AI gpt-oss-120b + Think DO; tools wrap MCP runtime

Credits are org-pooled. Deduction uses SQL WHERE credits >= cost RETURNING id. Failed queue consumers refund via failAiJobWithRefund.


8. Billing & tiers

Channel Role
Stripe Web Embedded Checkout + webhooks (legacy / still active until RC fulfillment cutover)
RevenueCat Cross-platform catalog; iOS StoreKit; Stripe app strp_ for external purchase sync; webhook → Ration

Free vs Crew (capacity): Inventory, meals, supply lists, owned groups, invites, and public share links are tier-gated. Crew includes 1 free Ask Ration conversation / group / day. Capacity is evaluated from the org owner’s effective tier (KV-cached ~60s).


9. External interfaces

9.1 MCP (ration-mcp)

  • Transport: streamable HTTP at /mcp
  • Auth: OAuth 2.1 (preferred) or API key
  • Tools: inventory, galley, manifest, supply, preferences, match, imports — uniform { ok, tool, data | error } envelope
  • Most tools are credit-free (rate-limited); credit-aware exceptions for plan-week / generate after host approval

9.2 Public REST v1

Scoped API keys for inventory / galley / supply export & import. OpenAPI at /api/openapi.json.

9.3 Mobile REST v1

Full product surface for iOS. OpenAPI at /api/openapi/mobile-v1.json.

9.4 Agent discovery

RFC-style /.well-known/ catalog, OAuth metadata, MCP server card, Apple AASA, DNS-AID HTTPS records under mayutic.com.


10. Scalability & reliability notes

Concern Design choice
Request latency Edge SSR + Smart Placement; heavy AI on Queues
D1 writer Single-region primary; compound indexes; org-scoped queries
KV Eventually consistent; rate-limit TTLs self-clean
AI cost abuse Credits + fail-closed rate limits + Flagship kill switches + Gateway spend limits
Idempotency Stripe/RC event IDs in KV; queue runIdempotentAiJob claim
Observability Analytics Engine datasets (ration_ops, ration_copilot) — no PII
GDPR delete Purge D1 + Vectorize + R2 for the user

11. Development & quality gates

Command Purpose
bun run dev:local / dev:remote Local Miniflare vs remote bindings
bun run test:unit Vitest
bun run typecheck / lint Types + Biome
bun run test:e2e Playwright (dev server for Option A)
bun run ios:generate / ios:check XcodeGen + build/test
bun run flag:check Flagship registry sanity
bun run db:generate / db:migrate:dev Schema migrations

Definition of done: unit tests + typecheck + lint pass; version bump in package.json + app/lib/version.ts (and iOS project.yml when ios/ ships); README updated when behaviour changes.

Versioning: Web 1.X.11.X.49 then 1.(X+1).0. iOS marketing version follows the same rule independently.


12. Where to go next (in-repo)

Doc Contents
README.md Full architecture, sequence diagrams, rate-limit matrix, MCP tool table, Copilot lifecycle
ios/README.md XcodeGen, signing, Xcode Cloud, auth, UI conventions
docs/dev/feature-flags.md Flagship registry & AI kill switches
docs/fin/ Support / Copilot knowledge corpus
docs/mcp/ MCP operator notes
.cursor/rules/ Engineering protocols (security, D1 patterns, testing, version bumps)

Maintained for project members. Prefer updating this page when the system topology changes (new Worker, tenancy model, or client architecture); keep workflow-level detail in README.md.