# M5 — Medication Grace — Session Card

> Planning artifact. A future build session opens this to know exactly what to build for module **M5 Medication Grace**, in what order, with what dependencies and done-criteria. No production code here.
> Read alongside `docs/plan/13-execution-modules-and-sessions.md` (the session framework this card implements), `03-protocol-engines.md §5` (the engine spec), `04-backend-togo.md` (schema/migration/plugin rules), and the relevant platform PRD per surface.

## Purpose

Medication Grace (Protocol Engine 5) gives users a humane, flexible window to log scheduled medications and tracks **adherence-to-logging** — never anything about the drugs themselves. The engine is a logging clock: it stores user-entered medication names and schedules verbatim, opens a **60-minute flexible grace window** per scheduled dose, and records each dose as `Logged` / `Missed` / `LateLogged`. Per Core Opinion 2 it performs **no drug-category inference, no dosage validation, and no interaction checking** — a `drug_safety_facts` table exists only as an empty seam reserved for a future licensed provider and stays empty in this build. Because all temporal/state logic lives server-side, this module also **closes legacy divergence #1 (Meds & Cycle missing on iOS)** purely by having each client consume the API — no client re-implements the grace window.

## Engine parameters (exact — do not invent)

| Parameter | Value | Source |
|---|---|---|
| Grace window | **60 minutes**, flexible, per scheduled dose | 03 §5.2 |
| Window model | abstract `[window_open, window_close]` spanning 60 minutes around/after the scheduled dose time | 03 §5.2 |
| Per-dose states | `Scheduled → GraceOpen → Logged` \| `GraceOpen → Missed → LateLogged` (late is flagged, never hidden) | 03 §5.3 |
| Schedule kinds | `fixed_times` / `interval` / `as_needed` (PRN has no scheduled dose ⇒ no grace window, no overdue) | module brief |
| Overdue handling | **advisory only**; a skipped dose **counts as kept** (adherence-to-logging), never a health judgment | legacy precedent, module brief |
| Drug reasoning | **prohibited** — no name→drug-class mapping, no dosage validation, no interaction checks | 03 §5.4, Core Opinion 2 |
| Duplicate dose log | second log for the same dose window rejected as duplicate, kept in audit trail | 03 §5.7 |
| Fail-safe | when the engine cannot determine which scheduled dose a log belongs to, it stores the log **unattached** and asks the user, never guesses | 03 §5.7 |
| Medical deferral | every medication-related uncertainty terminates in **"Consult your doctor."** | 03 §5.4, Core Opinion 2 |

**Reference REST surface (names only; the backend session owns final `/api/v1/` paths — clients conform to Phase 1, per 04 §7):**
`POST /v1/medications` (create schedule) · `GET /v1/medications` (list) · `PUT /v1/medications/{id}` (update) · `POST /v1/engines/medgrace/doses/{id}/log` (log within/after grace) · `GET /v1/engines/medgrace/state` (today's dose windows + states). *(Note: platform PRD sketches say `/api/v1/medications/entries` (web) and `/v1/engines/medication/log` (android); these are consumption sketches — the backend session normalizes the canonical paths and every client conforms.)*

## Sessions

Surface order within the engine (13 §3): **backend → web → mcp → chrome → apple → android → desktop.** The backend session must land before any client session. `<date>` and `NN` are placeholders filled at session start; `Mx` = `M5`.

| Session ID | Surface | Scope | Entry deps | Done criteria (13 §5 template) | Plan docs to read |
|---|---|---|---|---|---|
| `S-<date>-M5-backend-NN` | backend | `medicationgrace` engine as its own ToGO plugin: pure `core/` state machine (60-min grace window; `Scheduled/GraceOpen/Logged/Missed/LateLogged`; overdue-advisory-only; skipped-counts-as-kept) + `plugin.go` registration + expand-contract migration for **`medications`** (user-entered name verbatim), **`schedules`** (kind enum `fixed_times`/`interval`/`as_needed`), **`intakes`** (dose logs), and the empty-seam **`drug_safety_facts`** (reserved, stays empty — no inference) + endpoints returning computed dose state + engine unit tests + integration test. | M0.1–M0.4 (foundation backend: kernel, auth, plugin framework, migration/error harness); **M1 backend** (reference engine to copy). | **Backend-engine:** plugin registered on the kernel; migration expand-contract and **every column NOT NULL with a default**; endpoints return server-computed dose state; engine unit tests encode the 60-minute constant literally and pass; integration test vs. real Postgres passes (idempotency replay; **duplicate-dose-log rejected**; unattachable-log fail-safe); the `60min` grace constant appears **nowhere outside** the engine `core/` package. | 03 §5; 04 §1,§4,§5,§8,§9; 13 §3,§5 |
| `S-<date>-M5-web-NN` | web | Medication feature on the React+TanStack dashboard: medication **list + schedule CRUD** and **log-within-grace** at route `/engines/medication`; query key `['engine','medication',date]` invalidated by the log-medication mutation; renders per-dose grace state (open / logged / missed / late) verbatim from the server. | `S-<date>-M5-backend-*`; M0.5–M0.7 (web shell, theme engine, i18n engine); **M1 web** (reference card). | **Client-engine:** renders engine-endpoint state and submits log intents only; **thin-client/SSOT audit passes** (no 60-min window / grace / overdue logic in client — grep finds no protocol constant); both themes render per the strict color spec; Arabic RTL mirrors (incl. any progress/countdown bars); strings are ICU, none concatenated. | 05 §3 (routes/query keys/endpoints); 03 §5; 13 §5 |
| `S-<date>-M5-mcp-NN` | mcp | `log_medication` MCP tool (+ CLI equivalent): logs a medication within the 60-minute grace window by calling the real backend endpoint with the personal-access-token; sends `Idempotency-Key`; returns server dose state verbatim. | `S-<date>-M5-backend-*`; M0.8 (MCP server + CLI skeleton). | **MCP:** the tool calls the real API with PAT auth, returns server state verbatim, holds **no protocol logic and no AI keys**; excluded tools stay excluded (no `complete_break`, no diagnosis/analysis passthrough). | 11 (tool table, Core Opinions, auth); 03 §5; 13 §5 |
| `S-<date>-M5-chrome-NN` **(OPTIONAL)** | chrome | *Optional* MV3 surface: a simple medication reminder/log affordance (grace-window-open indicator on badge/popup + one-tap log intent). Whether meds appears in the extension at all is an open product question — **skip unless prioritized.** | `S-<date>-M5-backend-*`; **M1 chrome** (extension shell, poll worker, auth). | **Client-engine** (same as web): renders server state, submits log intent only; thin-client/SSOT audit passes; both themes + RTL correct; ICU strings. Reminder/badge visual is **PENDING DESIGN**. | 06 §3.2,§7,§9 (badge/popup/side-panel, optional-scope note); 03 §5; 13 §5 |
| `S-<date>-M5-apple-NN` | apple | Swift medication screens (iOS + relevant widget/complication surface): med list, schedule management, and log-within-grace, rendering the server's grace-window state. **This is the session that closes divergence #1 for meds** — screens only, zero client-side protocol logic. | `S-<date>-M5-backend-*`; **M1 apple** (suite shell, `HDDesignSystem` tokens, API client). | **Client-engine:** renders engine state, submits log intents only (server-confirmed writes only, no optimistic protocol writes); thin-client/SSOT audit passes; both themes per color spec; Arabic RTL mirrors; ICU strings, none concatenated. Screen layouts **PENDING DESIGN**. | 07 §11 (Divergence #1), §4 (consumption map), §10 (tokens); 02 §2.1; 03 §5; 13 §5 |
| `S-<date>-M5-android-NN` | android | Kotlin medication screens (phone + relevant Glance/Wear/Tile surface): med list, schedule management, log-within-grace; local reminder armed at server-provided instants only (client never computes the window). | `S-<date>-M5-backend-*`; **M1 android** (suite shell, tokens, API client). | **Client-engine:** renders server dose state, submits log intents only; local reminders fire at backend-computed instants (no client window math); thin-client/SSOT audit passes; both themes per color spec (physical-device check); Arabic RTL mirrors; ICU strings. | 08 § (medication rows: notification schedule + `medication/log`), §channels; 03 §5; 13 §5 |
| `S-<date>-M5-desktop-NN` | desktop | Electron medication surface, reusing the web components: med list + schedule + log-within-grace against the same API. | `S-<date>-M5-backend-*`; `S-<date>-M5-web-*` (component reuse); **M1 desktop** (Electron shell). | **Client-engine:** renders server state, submits log intents only; thin-client/SSOT audit passes; both themes + RTL correct; ICU strings. | 09 (desktop action map, medication row); 05 §3; 03 §5; 13 §5 |

**Session count: 7** (6 required + 1 optional chrome). Backend is the hard prerequisite for all six client sessions.

## Notes & gotchas

- **Legacy precedents worth reusing.** *Overdue = advisory only* and *skipped counts as kept* come straight from the legacy behaviour and align with Opinion 2 — the engine reports the logging fact and never nags, penalizes, or advises on a missed/skipped dose. Preserve this as engine behaviour, not client behaviour.
- **Hard Opinion-2 liability to avoid.** The engine must **never** map a medication name to a drug class, validate a dosage, or check interactions. `drug_safety_facts` is a **reserved empty seam** (all columns NOT NULL + default, but no rows and no read path in this build) awaiting a licensed provider — do not populate or infer it. Any medication-related uncertainty routes to **"Consult your doctor."**
- **Fail-safe on ambiguous logs.** If a dose log cannot be matched to a scheduled window, store it **unattached** and prompt the user; never guess the target dose. Duplicate logs for one window are rejected but kept in the audit trail.
- **`as_needed` (PRN) has no grace window.** Only `fixed_times` and `interval` schedules generate scheduled doses / overdue advisories; PRN meds are log-only. Keep this branch explicit in `core/`.
- **PENDING DESIGN / open decisions:**
  - **Anchor semantics (open question 03 §11.5):** whether the 60 minutes run strictly *after* the scheduled time, are *centered* (±30), or are *user-configurable* is undefined. Model abstractly as `[window_open, window_close]`; resolve before schema freeze.
  - **Timezone travel mid-schedule** — dose anchoring policy is an open question (shared with the day-boundary policy, 03 §0.5 / 04 open questions).
  - **Reminder/alert presentation across surfaces** — PENDING DESIGN (03 §5.4); med screen layouts (web, apple, android) — PENDING DESIGN.
  - **Chrome surface is optional** — whether Medication Grace appears in the extension (badge/popup/timeline) is an unresolved product question (06 §7,§9.2,§12); its reminder indicator is PENDING DESIGN. Skip the chrome session unless explicitly prioritized.
- **Path naming.** Platform PRDs sketch differing medication paths; the backend session is the authority that fixes canonical `/api/v1/` routes (04 §7), and every client conforms to Phase 1 — do not treat the client sketches as contract.

## Handoff reminder

At session end (per 13 §6): write **one `session-handoff` memory** to the `healthdebug` brain — `retain` the content in the §6 structure, then `memory/edit` to attach metadata `{project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"M5", title:"Handoff <session-id>", next:"<module+surface>"}` with a unique `source_ref: handoff/M5-medication-grace/<surface>/<n>` (never reused). 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.
