# M2 — Caffeine Block · Session Card

> Planning artifact. A build session opens this file (plus the brain — see §5 Handoff) to know exactly what its cell is, what must exist first, and when it is done. One session builds one `(module × surface)` cell. Surface order is fixed: **backend → web → mcp → chrome → apple → android → desktop** (doc 13 §3–§4). The backend session MUST land before any client session for this module.

## Purpose

The Caffeine Block engine enforces the protocol rule that **no caffeine is consumed during the first 90 minutes after waking** (the cortisol-window habit for desk professionals). It is its own ToGO plugin: a pure `core/` state machine (`AwaitingWake → Blocked → Clear`) plus `plugin.go` registration, a schema migration, and endpoints. The backend is the Single Source of Truth for the 90-minute countdown; every client only renders server state (block active/clear, server-provided deadline) and submits log/wake **intents**. Classification of a logged drink as **clean vs. sugar-based is decided server-side** against a drink registry (a DB resource carrying `is_sugar_based`); clients never categorize. Consistent with Core Opinion 1 the engine is **categorical — it records a drink by *kind*, never milligrams** — and consistent with Core Opinion 2 a violation is stated as a protocol fact ("caffeine during block window"), never a physiological claim, and any AI commentary defers to "Consult your doctor."

## Engine / feature parameters (exact — do not invent)

| Parameter | Value | Source / status |
|---|---|---|
| Block duration | **90 minutes**, starting at the wake event (`CaffeineBlockMinutes = 90`) | Spec-fixed (doc 03 §2.2). |
| Blocked category | Caffeine-bearing items — **categorical membership, not mg** | Spec-fixed (Core Opinion 1). |
| Wake-up anchor source | **OPEN QUESTION.** Legacy inferred wake = `work_start − 1h` (never asked). Manual "I'm awake" log vs. wearable sleep-end (HealthKit / Health Connect) vs. both-with-precedence is undefined. | doc 03 §2.2 / §7 item 3; legacy candidate answer (doc 12). **PENDING DESIGN.** |
| Drink classification (clean vs. sugar-based) | Decided **server-side** via drink registry column `is_sugar_based`. Legacy seed: sugar-based = red bull / monster / energy drink / soda / cola; clean = cold brew / matcha / green tea / espresso / black coffee / americano; **unknown kinds treated as clean**. | Legacy precedent seed data (doc 12). Registry is a DB resource; classification is not a client concern. |
| Daily caffeine-log cap | Legacy `MaxDailyCaffeineLogs = 8` | Legacy precedent, **not spec-fixed — PENDING ratification**. Do not treat as a protocol constant until ratified. |
| Caffeine-log cooldown | Legacy `CaffeineLogCooldown = 1 min` | Legacy precedent, **not spec-fixed — PENDING ratification**. |
| Violation handling | **Accept-and-flag** (store the log, record the violation), not reject-at-API | doc 03 §0.5 / §2.3; global assumption pending ratification. |
| Fail-safe (no wake logged) | Engine does **not** guess a wake time; caffeine logs made in `AwaitingWake` are stored and marked `unevaluated`; user is prompted to log wake. No retroactive violation invented. | doc 03 §2.7 — hard invariant. |

**State machine (doc 03 §2.3):** `AwaitingWake` → (wake event) → `Blocked` → (90 min elapsed, server timer) → `Clear`. In `Blocked`, a caffeine-bearing log is a categorical violation recorded accept-and-flag. Day/cycle reset boundary (`Clear → AwaitingWake`) is an OPEN QUESTION.

**REST surface (names from doc 03 §2.6; note divergence below):** `POST /v1/engines/caffeine/wake` (record wake), `GET /v1/engines/caffeine/state` (snapshot: `{ state, block_remaining, wake_time, violations_today }`), `GET /v1/engines/caffeine/violations` (history). Caffeine consumption itself is a log event fanned out through the shared intake pipeline (doc 03 §9.1); the engine evaluates those events via a kernel hook. **PENDING DESIGN:** exact log endpoint path — doc 03/11 imply the shared intake log, doc 05 uses `/api/v1/intake/entries`, doc 06 uses `POST /caffeine/log`. Reconcile in the backend session and document the chosen path in the handoff.

## Sessions

Session ID pattern: `S-<date>-M2-<surface>-NN` (`<date>` = the build date, `NN` = per-day sequence; fill in at session start).

| Session ID | Surface | Scope | Entry deps | Done criteria (doc 13 §5) | Plan docs to read |
|---|---|---|---|---|---|
| `S-<date>-M2-backend-NN` | backend | The `caffeineblock` ToGO plugin: pure `core/` state machine (`AwaitingWake/Blocked/Clear`, 90-min block, accept-and-flag, no-wake fail-safe); `plugin.go` registration (routes, hook subscription on the shared intake log, injected clock, migration manifest); expand-contract migration for caffeine state + violations + the **drink registry resource** (`is_sugar_based`, `name_ar`, seeded from legacy lists); `is_sugar_based` classification done server-side; `wake`/`state`/`violations` endpoints returning computed state; engine unit tests encoding every spec constant literally; integration test vs. real Postgres. Wake anchor: implement a pluggable anchor with the legacy `work_start − 1h` inference as the default, isolated so a future decision swaps it — flag PENDING. | M0.1–M0.4 (backend foundation: kernel, auth, plugin framework, migration harness); **M1 Hydration backend done** (reference engine pattern to copy). Coordinate with M4 Trigger Families on the shared categorical catalog / `caffeine_bearing` flag (doc 03 §4, §9.1) — if M4 catalog is not yet built, the drink registry is defined here and reconciled later. | **Backend-engine done when:** plugin registered on the kernel; migration is expand-contract and **every column is NOT NULL with a default**; endpoints return server-computed state; engine unit tests encode every spec constant literally and pass; integration test against real Postgres passes (idempotency replay, log-cooldown/cap races); **no protocol constant appears outside the engine's `core/` package**. | 13, 03 §0/§2/§9, 04 §6/§8, 12 (legacy caffeine + drinks registry) |
| `S-<date>-M2-web-NN` | web | Caffeine Block card + detail route (`/engines/caffeine`): renders server state, animates a **display-only** countdown toward the server-provided block deadline (`#00FF33` neon-green glow in dark), log-clean-caffeine intent (kind pick only), wake-time change action. No local "is caffeine allowed?" logic. | M0.5–M0.7 (web shell, theme engine, i18n engine); **M2 backend done**; M1 Hydration web done (reference card pattern). | **Client-engine done when:** surface renders the engine endpoint state and submits log intents only; **thin-client/SSOT audit passes** (grep for 90/block-minute/cooldown constants and classification logic finds nothing in client code); both themes render per the strict color spec; Arabic RTL mirrors correctly (incl. any progress/countdown bar); strings are ICU, none concatenated. | 13, 03 §2, 05 §1/§timeline+cards, 04 (response shapes) |
| `S-<date>-M2-mcp-NN` | mcp | Two tools wired to the real API with personal-access-token auth: `log_caffeine` (log a caffeine drink by **kind only, never mg**; sends `Idempotency-Key`) and `today_status` (full computed day incl. caffeine block state, returned verbatim). Holds no protocol logic, no classification, no AI keys. | M0.8 (MCP server + CLI skeleton); **M2 backend done**. | **MCP session done when:** each tool calls the real API with PAT auth, returns server state verbatim, holds **no protocol logic and no AI keys**, and the excluded tools (no `complete_break`, no diagnosis passthrough) stay excluded. | 13, 11, 03 §2 |
| `S-<date>-M2-chrome-NN` | chrome | Popup clean-caffeine button + caffeine block indicator: badge/popup mirror the server snapshot (`GET /protocol/state`), 1-click clean-caffeine intent, server rejection during the block rendered verbatim as an alert-state response. No local pre-validation of the block rule. | **M2 backend done**; M1 chrome (MV3 shell, poller/badge engine, API client) as reference. Block indicator visual is PENDING DESIGN (doc 06). | **Client-engine done when:** surface renders server snapshot and submits intents only; thin-client/SSOT audit passes (no block/cooldown/classification logic in extension code); both themes per color spec; Arabic RTL mirrors; ICU strings, none concatenated. | 13, 06 §snapshot+logging, 03 §2 |
| `S-<date>-M2-apple-NN` | apple | iOS Caffeine Block card + relevant widget/watch surface: renders server state and block countdown, submits clean-caffeine + wake intents. Wearable sleep-end as a candidate wake anchor source stays PENDING (server decides). No local protocol logic. | **M2 backend done**; M1 apple as reference (X3 sensors/HealthKit coordination only if a wearable wake anchor is chosen — PENDING). | **Client-engine done when:** surface renders engine state and submits intents only; thin-client/SSOT audit passes; both themes per color spec; Arabic RTL mirrors; ICU strings, none concatenated. | 13, 07, 03 §2 |
| `S-<date>-M2-android-NN` | android | Phone Caffeine Block card + relevant Wear/Glance surface: renders server state and block countdown, submits clean-caffeine + wake intents. Health Connect sleep-end as a candidate wake anchor stays PENDING (server decides). No local protocol logic. | **M2 backend done**; M1 android as reference (X3 Health Connect coordination only if a wearable wake anchor is chosen — PENDING). | **Client-engine done when:** surface renders engine state and submits intents only; thin-client/SSOT audit passes; both themes per color spec; Arabic RTL mirrors; ICU strings, none concatenated. | 13, 08, 03 §2 |
| `S-<date>-M2-desktop-NN` | desktop | Electron Caffeine Block surface, reusing the web card/components; renders server state + block countdown, submits clean-caffeine + wake intents through the shared API client. | **M2 backend done**; **M2 web done** (component reuse); M1 desktop as reference. | **Client-engine done when:** surface renders engine state and submits intents only; thin-client/SSOT audit passes; both themes per color spec; Arabic RTL mirrors; ICU strings, none concatenated. | 13, 09, 05, 03 §2 |

## Notes & gotchas

- **Reuse M1's shape.** Hydration is the reference engine; copy its plugin layout (`core/` + `plugin.go` + sqlc queries), its `/today` integration, its client card pattern, and its test harness. M2 sessions should be faster because the pattern is set.
- **Legacy seed data is ready.** The legacy `drinks` table (with `name_ar`) and the clean/sugar-based kind lists (doc 12) are a ready seed for the drink registry — reuse the classification lists and the admin-CRUD precedent rather than reinventing. Keep `is_sugar_based` server-side only.
- **Wake anchor is the load-bearing open question (PENDING DESIGN).** Do not hardcode a single source. Implement the legacy `work_start − 1h` inference as an isolated, swappable default so the eventual decision (manual log vs. wearable sleep-end vs. precedence) is a one-place change. Multiple wake events (naps/corrections) precedence and the `Clear → AwaitingWake` day-reset boundary are also undefined — fail safe, do not guess.
- **Categorical only (Core Opinion 1).** An espresso and a cola are equally "caffeine-bearing." Never compute mg, half-life, or tolerance. Log by kind. `check`/classification is boolean membership against the catalog/registry.
- **No invented certainty (Core Opinion 2).** Violations are protocol facts, not physiological claims; AI commentary on caffeine patterns defers to "Consult your doctor."
- **PENDING ratification, not spec-fixed:** `MaxDailyCaffeineLogs = 8` and `CaffeineLogCooldown = 1 min` are legacy precedents only — do not encode them as protocol constants until ratified, and if used provisionally, keep them inside `core/` and flag them in the handoff.
- **Endpoint-path divergence (PENDING DESIGN):** docs 03/05/06/11 name the caffeine log path differently (shared intake log vs. `/api/v1/intake/entries` vs. `/caffeine/log`). Reconcile in the backend session and record the canonical path in the handoff so all client sessions use it.
- **Shared intake pipeline liability.** Caffeine, GERD, and Trigger Families all evaluate the *same* intake log event via kernel hooks (doc 03 §9.1). Coordinate the categorical catalog / `caffeine_bearing` flag with M4 so the drink registry and the food catalog do not fork.
- **NOT-NULL + expand-contract is enforced by CI.** Every new column (caffeine state, violations, drink registry) must be NOT NULL with a defined default, and the migration must be expand-contract — the schema lint gate (M0.1) will fail otherwise.
- **MCP stays thin.** `log_caffeine`/`today_status` return server state verbatim; no protocol logic, no classification, no AI keys. Excluded tools stay excluded.

## Handoff reminder

At session end, follow doc 13 §6: write **one** `session-handoff` memory to the `healthdebug` brain (`retain`, then `memory/edit` to attach metadata) with `source_ref: handoff/M2-caffeine-block/<surface>/<n>` (unique — never reuse) and `metadata` `{project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"M2", title:"Handoff <session-id>", next:"<module+surface>", ...}`, using the exact content structure in doc 13 §6. 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 (e.g., the wake-anchor resolution) also gets its own classified memory and, if it changes the plan, an edit to the relevant plan doc.
