# Session Card — M3 GERD Window

> Per-module session list for the **GERD Window** protocol engine (Module M3, row 3 of the module × surface grid in `docs/plan/13-execution-modules-and-sessions.md`). Each row below is one build session — one (module × surface) cell. Open this card at the start of a GERD session, read the referenced plan docs, build exactly that cell, then close with a brain handoff (see the bottom of this file and doc 13 §6).

## Purpose

The GERD Window engine protects the pre-sleep digestive window for GERD-prone users: during the **4 hours before sleep**, the *only* protocol-compliant intake is an explicit closed whitelist — **water, chamomile, anise**. Anything else logged inside the window is a categorical violation (a sip of juice and a full meal are the same violation class — quantity is irrelevant, per Core Opinion 1). The engine is its own ToGO plugin (pure `core/` state machine + `plugin.go` registration) and is the single source of truth: it computes window activation server-side from a sleep anchor, records violations, and exposes state. Every client surface only *renders* `window_starts_at` / `window_active` / `allowed_items` / `violations` and submits intake intents — no client ever computes the 4-hour window or the whitelist membership. The check is a **categorical allowed-list membership test only**; there is no quantitative math and no medical claim (the engine reports compliance, never "reflux occurred," and defers to "Consult your doctor").

## Engine parameters (exact — from doc 03 §3; do not invent)

| Parameter | Value | Source |
|---|---|---|
| Window length | **4 hours** before sleep | 03 §3.2 |
| Whitelist (ONLY items allowed) | **water, chamomile, anise** — a *closed* 3-item set | 03 §3.2 / §3.4 |
| Whitelist semantics | Set membership; no "small amounts are fine" carve-out (quantity irrelevant, Opinion 1) | 03 §3.4 |
| Window activation | Computed **server-side** from the sleep anchor; clients render only `window_starts_at` / `window_active` | 03 §3.4 |
| No-anchor behavior | **Fail-safe** — `state: unanchored`; engine does not guess a bedtime; **no violations generated** while unanchored | 03 §3.7 |
| Alert / violation color | **`#FF3333`** red (dark: with glow; light: flat) | 03 §3.5, color spec |
| CarPlay/Auto pre-alert lead time | **30 minutes** before window start ("GERD Window starting in 30 minutes; finish eating before driving.") — spec *example*; whether this offset is a general backend rule is **PENDING DESIGN** (see Notes) | 07 §241/§351, 08 §314 |

**States (03 §3.3):** `unanchored` → `Open` (daytime; catalog eval deferred to Engine 4) → `WindowActive` (inside the 4 h window; whitelist is the *entire* allowed set; non-whitelist log → violation recorded) → `Sleeping` (between sleep and wake events; logs store-and-flag-for-review — spec-undefined, see gotchas) → `Open` on wake (hands off to the Caffeine Block engine).

**REST surface (names only, 03 §3.6):** `GET /v1/engines/gerd/state` · `PUT /v1/engines/gerd/sleep-anchor` · `POST /v1/engines/gerd/sleep` · `GET /v1/engines/gerd/violations`. Intake is logged through the shared pipeline `POST /v1/intake/logs`, which fan-outs via Microkernel hooks to GERD, Caffeine, and Trigger Families evaluators — GERD records its own verdict independently (03 §9.1).

## Sessions

Build order within the module (doc 13 §3/§4): **backend → web → mcp → chrome → apple → android → desktop.** The backend session MUST land before any client session for this module. Session ID pattern: `S-<date>-M3-<surface>-NN` (fill `<date>` = build date, `NN` = zero-padded ordinal at write time).

| Session ID | Surface | Scope (this one session) | Entry deps | Done criteria (doc 13 §5) | Plan docs to read |
|---|---|---|---|---|---|
| `S-<date>-M3-backend-NN` | backend | The `gerd` ToGO plugin: pure `core/` state machine (`unanchored`/`Open`/`WindowActive`/`Sleeping`) computing the 4 h pre-sleep window from the sleep anchor; closed-whitelist membership check `{water, chamomile, anise}`; `violated` recording on non-whitelist intake; warning-lead-time hook (fire "window starting" event ahead of `window_starts_at`). `plugin.go` registration on the kernel + hook-bus subscription to `POST /v1/intake/logs` fan-out. Expand-contract migration for sleep-anchor config + violations tables. Endpoints: `state`, `sleep-anchor` (PUT), `sleep` (POST), `violations` (GET). Fail-safe `unanchored`. Injected clock. | M0.1–M0.4 (kernel, auth, plugin framework, migration harness); M1 backend (reference engine pattern to copy). | **Backend-engine template:** plugin registered on kernel; migration expand-contract and **every column NOT NULL with a default**; endpoints return *computed* state (decisions, not raw params); engine unit tests encode every spec constant literally (4 h window, the exact 3-item whitelist, `unanchored` fail-safe) and pass; integration test against real Postgres passes — idempotency replay, plus GERD edge cases (unanchored → no violations, bedtime-moved recompute, `Sleeping` store-and-flag, seeded *in-flight open window* survives roll-forward/back); **no protocol constant (4 h, whitelist items) appears outside the `gerd` `core/` package**. | 03 §3 (+§9.1), 04 (§ plugin layout, timezone/day-boundary open Q), 13 §5, 01 (NOT NULL + expand-contract; seed in-flight GERD window), 12 (legacy `shutdown.go` precedent) |
| `S-<date>-M3-web-NN` | web | GERD Window timer card on the dashboard (`/engines/gerd`): renders server state only — `window_starts_at` countdown, `window_active`, allowed-items chips (water/chamomile/anise), violation list. **Red `#FF3333` on violation.** `unanchored` state → prompt to configure sleep anchor. TanStack query key `['engine','gerd',date]`; invalidated on food/drink log mutation. | M3 backend; M0.5–M0.7 (web shell, theme engine, i18n engine). | **Client-engine template:** card renders the engine endpoint state and submits log intents only; **thin-client/SSOT audit passes** (grep for `4h`/window/whitelist logic in client code finds nothing); both dark-glow and light-flat themes render per the strict color spec; **Arabic RTL mirrors correctly incl. the countdown/progress bar**; all strings ICU Message Format, none concatenated. | 05 (§ query keys, engine status cards), 03 §3, 13 §5, 01 |
| `S-<date>-M3-mcp-NN` | mcp | Surface the GERD countdown inside the `today_status` tool: `state`, `window_starts_at`, `window_remaining`, `allowed_items`, `violations` — passed through verbatim as part of the full computed day. No new write tool. | M3 backend; M0.8 (MCP server + CLI skeleton, PAT auth, transport). | **MCP template:** the tool calls the real API with personal-access-token auth, returns server state **verbatim**, holds no protocol logic and no AI keys; excluded tools stay excluded (no `complete_break` etc.). | 11 (`today_status`), 03 §3, 13 §5 |
| `S-<date>-M3-chrome-NN` | chrome | MV3 surface: **badge red bug icon** while a GERD-window violation is active; **GERD timer in the popup** (4 h countdown/state, alert styling `#FF3333` on violation). Reads the aggregate `GET /protocol/state` snapshot (badge/timer driven by server state); logging buttons submit intents only. | M3 backend; M1 chrome (reference MV3 shell: service worker, popup, badge, `/protocol/state` polling pattern). | **Client-engine template:** popup/worker render server snapshot and submit intents only; **thin-client/SSOT audit passes** (no window/whitelist logic in worker or popup — grep finds nothing; badge state derived from server verdict); both themes; RTL correct; ICU strings, none concatenated. Red-bug-icon asset is **PENDING DESIGN** — wire the state, use a placeholder if art not delivered. | 06 (popup, badge states, `/protocol/state`), 03 §3, 13 §5 |
| `S-<date>-M3-apple-NN` | apple | iOS/macOS GERD card (render window state + permitted list *exactly as returned*); **watchOS haptic at window start**; **CarPlay audio-first alert** — voice "GERD Window starting in 30 minutes; finish eating before driving." Alert *times* come from the server-computed alert schedule; the client only schedules locally and voices. Widget content is a candidate — PENDING. | M3 backend; M1 apple (reference Swift shell: iOS/watch/CarPlay wiring); X3 sensors *alongside* (HealthKit sleep push may feed the anchor). | **Client-engine template:** every Apple surface renders engine state and submits intents only; **thin-client/SSOT audit passes** (no 4 h/window logic on device; alert times taken from the server schedule, not computed); both themes; Arabic RTL mirrors; ICU strings, none concatenated. Watch haptic/CarPlay copy voiced from localized ICU keys. | 07 (§ GERD row, CarPlay audio-first, watch haptic, alert schedule), 03 §3, 13 §5 |
| `S-<date>-M3-android-NN` | android | **Android Auto warning** for the GERD window (approaching/active) with the allowed-items framing (only water, chamomile, anise) *exactly as delivered by the backend*; handheld GERD status tile; a GERD-Window notification channel fed by `GET /v1/notifications/schedule`. Renders server state; no local window math. | M3 backend; M1 android (reference Kotlin shell: phone/Auto/Glance wiring); X3 sensors *alongside* (Health Connect sleep push may feed the anchor). | **Client-engine template:** phone/Auto/tile render server state and submit intents only; **thin-client/SSOT audit passes** (no window/whitelist logic; allowed-items list rendered verbatim); both themes; Arabic RTL mirrors; ICU strings, none concatenated. | 08 (§ GERD row, Auto warning, notification schedule/channels), 03 §3, 13 §5 |
| `S-<date>-M3-desktop-NN` | desktop | Electron GERD surface reusing the M3 web components: window card + tray/OS notification on GERD violation (`#FF3333`). Shows *what the server said* on rejection; never pre-computes it. | M3 backend; M3 web (reuses web components); M1 desktop (reference Electron shell/tray). | **Client-engine template:** tray/window render server state and submit intents only; **thin-client/SSOT audit passes** (no window/whitelist logic in Electron/main or renderer); both themes; Arabic RTL mirrors; ICU strings, none concatenated. | 09 (§ SSOT, tray rejection flow), 05, 03 §3, 13 §5 |

**Session count: 7** (backend, web, mcp, chrome, apple, android, desktop).

## Notes & gotchas

- **Legacy precedent worth reusing (doc 12, `shutdown.go`):** window = `shutdown_window_hours` before target sleep, **default 4 h**; allowed = water/chamomile/anise; **"past sleep time rolls to tomorrow."** Reuse the default-4h and roll-to-tomorrow anchoring logic. Legacy `notify.go` fired a **shutdown warning at 30 min** before the window — corroborates the CarPlay/Auto 30-minute pre-alert as a reasonable default. **Do NOT port** the legacy high-risk keyword *blocklist* (spicy, fried, dairy, chocolate, citrus, tomato, coffee, soda, alcohol, mint): the greenfield GERD engine is a closed *whitelist* membership check, not a keyword blocklist — that blocklist territory belongs to Trigger Families (Engine 4 / M4).
- **PENDING DESIGN — sleep-anchor source (primary open question, doc 03 §3.2/§11.4, doc 04 § Open Questions).** The window is *pre*-sleep but the spec does not fix the anchor: configured bedtime vs. wearable-predicted sleep onset vs. explicit "going to sleep now" event, plus timezone/day-boundary/DST policy. The backend session must pick a workable default (configured bedtime, roll-to-tomorrow) behind the `sleep-anchor`/`sleep` endpoints and flag the choice in its handoff. Do not hardcode a bedtime.
- **PENDING DESIGN — recompute policy when bedtime moves after the window started** (03 §3.7): whether already-flagged logs are re-evaluated is undefined. Choose store-and-annotate, do not silently rewrite violation history.
- **PENDING DESIGN — logs during `Sleeping`** (03 §3.7): spec-undefined; proposed handling is **store-and-flag-for-review, not auto-violation.** Keep it out of the violation stream unless design says otherwise.
- **PENDING DESIGN — UI/asset items:** window-countdown + whitelist-chip UI treatment (03 §3.5); chrome **red bug icon** asset (06 §70/§249); whether clients expose chamomile/anise quick-log buttons in addition to water (06 §159/§346, 09 rejection copy); Android notification-channel taxonomy/names (08 §222); whether the **30-minute pre-alert lead time** is a general backend alerting rule or CarPlay/Auto-specific (07 §351, 08 §314). Wire the state and use placeholders where art/copy is not delivered.
- **Liabilities to avoid:** clients must NEVER compute the 4-hour window or whitelist membership (SSOT — the API returns decisions/state, not raw parameters). The whitelist is *closed* — resist any "a little is fine" carve-out. A state row reading `NULL` is an undefined engine state — every column NOT NULL with a default; migrations expand-contract only; roll-forward/back tests must seed an **in-flight open GERD window** and assert it survives (01 §expand-contract).
- **Cross-engine coupling:** GERD, Caffeine Block, and Trigger Families all evaluate the *same* intake event via the `POST /v1/intake/logs` fan-out; each records an independent categorical verdict (a 10 p.m. cola can be caffeine-compliant, a GERD violation, and a trigger exposure at once — 03 §9.1). The sleep/wake anchor is shared with Caffeine Block — GERD's `wake` transition hands off to the Caffeine Block engine.
- **Catalog dependency gotcha:** whitelist membership needs stable identifiers for water/chamomile/anise. The shared categorical catalog is largely M4 (Trigger Families) territory, which is built *after* M3. Because the GERD whitelist is a *closed* 3-item set, keep those allowed identifiers in the `gerd` `core/` package (or seed a minimal 3-item allowed-list) so the backend session is not blocked on M4; reconcile with the shared catalog when M4 lands. Flag this in the handoff.

## Handoff reminder

At the end of every M3 session: write **one `session-handoff` memory** to the cabrain `healthdebug` brain — `retain` the content in the doc 13 §6 structure, then `memory/edit` to attach metadata (retain cannot set metadata). Use `source_ref: handoff/M3-gerd-window/<surface>/<n>` (unique — never reuse) and metadata `{project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"M3", title:"Handoff <session-id>", 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 new architectural decision or resolved/added open question also gets its own classified memory and, if it changes the plan, an edit to the relevant plan doc. See doc 13 §6 and `docs/brain/BRAIN-INDEX.md`.
