ページ · Health Debug
M4 — Trigger Families (nutrition) — Session Card
Per-module session plan for the Trigger Families protocol engine (Engine 4). Read alongside
docs/plan/13-execution-modules-and-sessions.md(the framework this card implements),03-protocol-engines.md§4 & §9,04-backend-togo.md, and the relevant platform PRD (05 web, 06 chrome, 07 apple, 08 android, 09 desktop, 11 mcp). One session builds one(module × surface)cell. Surface order is fixed: backend → web → mcp → chrome → apple → android → desktop, and the backend session MUST land before any client session for this module.
1. Purpose
Trigger Families is the categorical food/intake catalog that underpins the whole protocol. It is Engine 4, but it is really a classification service + per-user exposure ledger: every catalog item is strictly Safe or Trigger-bearing, and each trigger-bearing item belongs to one or more named trigger families — Gout, IBS-GERD, Fatty Liver. This module institutionalizes Core Opinion 1 (categorical, not quantitative): there is no portion, gram, calorie, purine-milligram, glycemic, or risk-score math anywhere in the food path — ever. It ships the trigger-families ToGO plugin plus the shared categorical catalog_items DB resource (which Engines 2 Caffeine and 3 GERD also consume via boolean membership flags), a classify/lookup endpoint, and the categorical verdict surfaces on each client (web meal-log + trigger checker, mcp log_meal/check_trigger, the chrome Contextual Trigger Scanner, and the native/desktop food-log screens). Legacy nutrition.go / FoodRegistry.swift lists and the food_items + drinks tables are seed data only, not logic to port.
2. Engine parameters (exact — do not invent)
| Parameter | Value |
|---|---|
| Item classification | Strictly binary: Safe / Trigger-bearing (no third quantitative state) |
| Trigger families | Gout, IBS-GERD, Fatty Liver |
| Quantities | None. Ever. No portion / gram / calorie / percentage / threshold / risk score in any food surface (Core Opinion 1) |
| Unclassified fail-safe | Unknown item ⇒ no protocol judgment: unknown ≠ safe, unknown ≠ trigger; shown as "unevaluated — consult your doctor if unsure". AI is prohibited from guessing a classification; curation is human. |
| Multi-family membership | The categorical model allows an item to belong to >1 family simultaneously (assumed true — PENDING DESIGN confirmation) |
| Shared-catalog flags | caffeine_bearing (consumed by Engine 2) and gerd_whitelisted = exactly water / chamomile / anise (consumed by Engine 3) live on the same catalog_items row |
| Classification enum | unclassified | safe | trigger_bearing |
Catalog SQL sketch (planning artifact — every column NOT NULL with a default, per schema rule):
REST surface (names from 03 §4.6 — indicative, frozen by the backend session):
GET /v1/catalog/items · GET /v1/catalog/items/{id} · POST /v1/catalog/items/proposals · POST /v1/intake/logs (shared entry point, fan-out via Microkernel hooks to Engines 2/3/4) · GET /v1/engines/triggers/exposures (per-user timeline by family) · POST /triggers/scan { text, locale } (chrome scanner → per-family Safe | Trigger-bearing | Unknown).
3. Sessions
Session ID pattern: S-<date>-M4-<surface>-NN (leave <date> and NN as placeholders for the session that opens the cell).
| Session ID | Surface | Scope | Entry deps | Done criteria (doc 13 §5) | Plan docs to read |
|---|---|---|---|---|---|
S-<date>-M4-backend-NN | backend | trigger-families plugin (pure core/ classifier state machine + plugin.go) + catalog_items DB resource; classify/lookup endpoints (GET /v1/catalog/items[/{id}], POST /v1/catalog/items/proposals); wire POST /v1/intake/logs fan-out so each intake writes a per-user exposure record {item, families[], evaluated_as: safe|trigger, engines_notified[]}; GET /v1/engines/triggers/exposures; POST /triggers/scan categorical taxonomy lookup; seed the catalog from legacy nutrition.go/FoodRegistry.swift lists + food_items/drinks as seed data (not logic). | M0.1, M0.2, M0.3 (plugin framework + hook bus), M0.4 (expand-contract migration harness) | Plugin registered on the kernel; migration is expand-contract and every column NOT NULL with a default; endpoints return computed categorical state (never quantities); engine unit tests encode Safe/Trigger-bearing + the three families + unclassified fail-safe literally and pass; integration test against real Postgres passes (idempotent re-log replay, multi-family membership, unclassified → no-judgment); no protocol constant/family label appears outside the engine's core/ package. | 03 §4 & §9, 04, 12 (§seed rows) |
S-<date>-M4-web-NN | web | Trigger Families dashboard card at /engines/triggers: meal/food log control (categorical pick only) + trigger checker showing the backend's per-family verdict; renders Safe / Trigger-bearing / unevaluated verbatim; exposure timeline by family. | M0.5, M0.6, M0.7 (web shell + theme + i18n); M4 backend | Renders the trigger endpoint / /today state and submits log intents only; thin-client/SSOT audit passes (no food categorization, no quantity/threshold math in client — grep for family labels/constants finds nothing); both themes render per the strict color spec; Arabic RTL mirrors correctly (incl. any progress/timeline bar); strings are ICU, none concatenated (family labels never concatenated into sentences). | 05 §1–§7, 03 §4 |
S-<date>-M4-mcp-NN | mcp | log_meal tool → logs a food item and returns its Trigger Family classification (Gout / IBS-GERD / Fatty Liver / Safe); check_trigger tool → answers Safe / Trigger-bearing only for a named food/ingredient. Idempotency-Key on log_meal. | M0.8 (mcp server + CLI skeleton); M4 backend | Each tool calls the real API with personal-access-token auth, returns server state verbatim, holds no protocol logic and no AI keys; tool schema descriptions state outputs are behavioral-tracking data, not medical advice; no quantity ever enters a tool input/output; excluded tools stay excluded (no diagnosis-shaped tool). | 11, 03 §4 |
S-<date>-M4-chrome-NN | chrome | Contextual Trigger Scanner: chrome.contextMenus entry (contexts:["selection"], no content script) → service worker POST /triggers/scan { selectionText, locale } → render per-family categorical verdict (Safe | Trigger-bearing | Unknown). Extension only ever sees the categorical result. | M4 backend (/triggers/scan); M1 chrome (extension shell + TOGO auth + service worker/context-menu infra) | Surface renders server verdict and submits the selection intent only; thin-client/SSOT audit passes (no categorization, no quantity/score in extension code); both themes per spec; Arabic RTL on the result surface, ICU strings (en+ar context-menu label), none concatenated; unknown/ambiguous text shown as the server's "unknown" as-is (extension never guesses); AI text-extraction-vs-classification boundary respected — AI may only help extract candidate item text, never assign a family (see gotchas). | 06 §2, §3.2, §6, 03 §4 |
S-<date>-M4-apple-NN | apple | iOS food-log + trigger surface: Safe / Trigger-bearing labels (Gout / IBS-GERD / Fatty Liver) from API; categorical pick logging; relevant watch/widget copy shows labels only. | M4 backend; M1 apple (workspace scaffold, HDAPIClient, TOGO auth session, theme/ICU-RTL pipeline) | Surface renders API state and submits log intents only; thin-client/SSOT audit passes (no local food categorization on any Apple surface incl. Siri/CarPlay/watch/widget); both themes per spec; Arabic RTL mirrors; ICU strings, none concatenated; no diagnosis/quantity phrasing anywhere. | 07 §engine-responsibility table, 03 §4 |
S-<date>-M4-android-NN | android | Kotlin phone food-log + categorical verdict display (Gout / IBS-GERD / Fatty Liver vs Safe); Glance/Wear copy shows labels only; no local categorization. (Health Connect nutrition write-back belongs to X3, not here.) | M4 backend; M1 android (suite scaffold, API client/model, TOGO auth, theme/ICU-RTL) | Surface renders API verdict and submits log intents only; thin-client/SSOT audit passes (no categorization/quantity in phone/Wear/Auto/Glance code); both themes per spec; Arabic RTL mirrors; ICU strings, none concatenated; en/ar string parity. | 08 §engine-responsibility table, 03 §4 |
S-<date>-M4-desktop-NN | desktop | Electron food-log + trigger surface, reusing the web M4 components inside the desktop shell; renders categorical verdict only. | M4 backend; M4 web (component reuse); M1 desktop (Electron shell) | Surface renders server state and submits log intents only; thin-client/SSOT audit passes (reused web components carry no protocol logic); both themes per spec; Arabic RTL mirrors; ICU strings, none concatenated. | 09, 05 (reused components), 03 §4 |
4. Notes & gotchas
- Shared catalog = cross-engine dependency (coordination flag).
catalog_itemscarriescaffeine_bearing(Engine 2 / M2) andgerd_whitelisted(Engine 3 / M3) on the same row. Module order is M1→M2→M3→M4, but the catalog Engine 2/3 read from is owned here in M4. Do not restructure the module order silently — instead the M4 backend session must expose these membership flags (already in the schema sketch), and M2/M3 backends must consume them rather than hardcoding food lists. If M2/M3 landed before M4, confirm whether they stubbed membership; reconcile in the handoff. Flag any resolution as an architecture-decision memory. - Legacy is seed, not logic.
nutrition.gouses lowercased substring matching that errs toward flagging (ibs_gerd: whole eggs/falafel/deep-fried/raw onion/raw garlic/cheddar/yellow cheese; gout: red meat/liver/duck/beans/lentils/legumes; fatty_liver: refined sugar/honey/nutella/jam/white flour/mixed carbs; plus safe lists). Mine the item lists andname_arvalues as a ready seed dataset (with admin-CRUD precedent), but do not port the substring-matching algorithm as protocol logic — classification is explicit per-item catalog membership, not fuzzy string matching. - AI text-extraction-vs-classification boundary (chrome — OPEN QUESTION / PENDING DESIGN). Whether
/triggers/scanis a pure server-side taxonomy lookup or may use the BYOK AI proxy is a backend Phase 1 decision (06§6.2, open Q #12). The hard invariant either way: AI may only help extract candidate item text from the highlighted selection; it must NEVER assign or guess a Safe/Trigger-bearing verdict or a family. Classification is human-curated catalog lookup only. The extension only ever sees the finished categorical result. - Unclassified fail-safe is load-bearing. Free-text the catalog does not know is
Unclassifiedand yields no judgment — never silently "safe". Surface it as "unevaluated — consult your doctor if unsure". - Reclassification audit honesty. Historical exposure records keep the classification as evaluated at log time; the recomputation policy on later reclassification is an open question — do not invent one.
- Violation UX (PENDING DESIGN). Whether a categorically disallowed intake is rejected or accepted-and-flagged is unsettled (
03§0.5 assumes accept-and-flag). Alert rendering is PENDING DESIGN. - Catalog curation ownership (OPEN QUESTION). Who curates/seeds the catalog (admin team / clinical advisors / future doctor dashboard) is not defined.
- Core Opinion 2. Family membership is protocol categorization, never "this food caused your flare." Any AI-adjacent copy is behavioral pattern analysis that must terminate in "Consult your doctor" — placement/wording PENDING DESIGN.
- i18n. Item names via ICU message keys; never concatenate family labels into sentences in code; full Arabic RTL incl. mirrored timeline/progress bars.
5. Handoff reminder
At session end, write one session-handoff memory to the healthdebug brain (retain, then memory/edit to attach metadata — retain cannot set metadata) using the doc 13 §6 content structure, with a unique source_ref: handoff/M4-trigger-families/<surface>/<n> and metadata { project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"M4", title:"Handoff <session-id>", tags:[...], next:"<module+surface>" }. Then update the single build-ledger memory (source_ref: build-ledger, edited in place) so one recall shows the whole frontier and the recommended next session. Any architectural decision or new open question also gets its own classified memory and, if it changes the plan, an edit to the relevant plan doc. See docs/plan/13-execution-modules-and-sessions.md §6 and docs/brain/BRAIN-INDEX.md.