# Session Card — Module X3: Sensors & Biometrics

> Planning artifact. A future build session opens this card, reads the referenced plan docs and the latest `session-handoff` for the target cell, builds exactly one cell, then writes its own handoff. No production code lives here.

## Purpose

Module X3 gives Health Debug its device-sensor bridge and the body-metrics feature that sits on top of it. The ToGO backend gains a **biometrics ingest** path plus a **body-metrics module** (its own ToGO plugin) that stores raw health samples verbatim and computes a **baseline-from-earliest-reading progress model** server-side. The native clients read the device health stores **on-device only** — Apple via HealthKit, Android via Health Connect — serialize new samples to JSON, and push them to ToGO; they also write Health Debug's own confirmed logs back into the device store for ecosystem parity. The web client renders read-only body-metrics display cards. Consistent with the two Core Opinions, **no client ever interprets a biometric** (no thresholds, no trend copy, no "your weight is concerning"): samples are transported and rendered verbatim, all analysis is backend behavioral-pattern analysis only, and any surfaced conclusion always defers to "Consult your doctor." This is a cross-cutting module, so it runs **alongside the Apple/Android engine sessions** (doc 13 §4), and — like every module — its backend session must land before any client session.

## Engine / feature parameters relevant to this module

Use these exact values. Do not invent numbers. Items are tagged **[spec-fixed]**, **[legacy precedent — PENDING ratification]**, or **[PENDING DESIGN]**.

- **Body-metrics scope (backend, superset):** Weight, Body Fat %, Body Water %, Heart Rate, Skin Temp, Sleep. **[spec-fixed]**
- **Progress model:** baseline is the **earliest logged reading**; progress is computed from that baseline forward, **server-side** (a legacy precedent reused; clients never compute it). **[legacy precedent — PENDING ratification]**
- **Apple HealthKit read scope → push:** Weight (`bodyMass`, direct), Body Fat % (`bodyFatPercentage`, direct), Body Water % (**no first-party HealthKit type exists** — source undefined), Heart Rate (`heartRate`, direct), Skin Temp (`appleSleepingWristTemperature` and/or `bodyTemperature` — mapping undefined). Apple's §5 read scope has **no Sleep read**. **[spec-fixed scope; Water% + Skin Temp mapping = PENDING DESIGN]**
- **Apple HealthKit write-back:** exactly two things — (1) **burned active calories** (backend-provided figures), (2) **completed hydration** (each server-accepted **250 ml** water log written as a dietary water sample). Only server-confirmed entries; samples tagged with the backend entry ID for idempotency. **[spec-fixed]**
- **Android Health Connect read scope → push:** Weight, Body Fat %, Heart Rate, Sleep (sessions/wake time). Android has **no Body Water %** and **no Skin Temp**. Permission groups requested **at point of use**. **[spec-fixed]**
- **Android Health Connect write-back:** hydration (accepted 250 ml unit → Hydration record) and nutrition/food (categorical model → quantitative Health Connect record mapping is unresolved). Only **backend-accepted** logs are mirrored; a cooldown- or cap-rejected hydration tap is never written. **[spec-fixed; nutrition mapping = PENDING DESIGN]**
- **Cross-scope reconciliation:** Apple lacks Sleep read; Android lacks Water % + Skin Temp. The unified backend body-metrics scope must tolerate per-platform partial coverage without treating an absent type as an error. **[PENDING DESIGN — open question]**
- **Hydration write-back units carried from M1:** 250 ml logging unit, 5000 ml/day cap, 30-second cooldown are enforced **server-side** by the Hydration engine; X3 clients only mirror the *accepted* result, never re-decide it. **[spec-fixed]**
- **Legacy weigh-in window 07:00–11:00** existed in the legacy `notify.go` for weigh-in nudges. It is a **legacy precedent only**, not a greenfield spec constant — do not bake it into the body-metrics module without ratification. **[legacy precedent — PENDING ratification]**
- **Schema rule:** `health_samples` and `daily_summaries` — every column **NOT NULL with an explicit default**; all migrations strictly **expand-contract**; sqlc + Atlas; raw samples stored verbatim (raw-sensor push model). **[spec-fixed]**

## Session table

Surfaces in scope for X3: **backend → web → apple → android** (per the surface order; `mcp`, `chrome`, `desktop` are out of scope for this module — biometrics do not surface in a browser popup, the brief names no MCP body-metrics tools, and desktop reuses the web cards). Backend precedes all clients.

| Session ID | Surface | Scope (this one session builds) | Entry deps | Done criteria (doc 13 §5 template) | Plan docs to read |
|---|---|---|---|---|---|
| `S-<date>-X3-backend-NN` | backend | Biometrics ingest endpoint(s) + the **body-metrics module as its own ToGO plugin**: accept batched biometric samples (Weight, Body Fat %, Body Water %, Heart Rate, Skin Temp, Sleep) and store them **verbatim** in `health_samples`; roll per-day state into `daily_summaries`; compute the **baseline-from-earliest-reading** progress model server-side; expose a read endpoint returning that computed state. No interpretation/thresholds. | M0.1–M0.4 (kernel/config, TOGO auth, plugin framework, expand-contract migration harness) | **Backend-engine template:** module registered on the kernel as its own plugin; `health_samples` + `daily_summaries` migrations are expand-contract with **every column NOT NULL + explicit default**; ingest is **idempotent** (same batch/key ⇒ one row, replayed result) and stores raw samples verbatim; read endpoint returns **server-computed** baseline/progress state (no client math); unit tests encode the baseline-from-earliest + progress logic literally and pass; integration test against real Postgres passes (idempotent-replay, cross-user isolation **404-not-403**); no threshold/interpretation logic anywhere (analysis defers to backend AI + "Consult your doctor"); no protocol constant leaks outside the module's `core/` package. | 13 §3/§4/§5; 04 (§1 layout, §3 sqlc+Atlas, §4 NOT-NULL rule, §5 expand-contract, §8 plugin purity, §9 testing); 03; 01 (Core Opinions); 12 (legacy `health_samples`, weigh-in precedent) |
| `S-<date>-X3-web-NN` | web | **Body-metrics display cards**: render the server-computed body-metrics state (latest reading + baseline + progress per type) fetched from the X3 read endpoint. Read-only; no logging, no interpretation. | `S-<date>-X3-backend-NN`; M0.5 (web shell + typed API client + auth), M0.6 (theme), M0.7 (i18n/RTL) | **Client-engine template:** cards render the X3 endpoint state and never compute baseline/progress locally; **thin-client/SSOT audit passes** (grep finds no biometric thresholds, no baseline math, no protocol constants in client code); both dark-glow and light-flat themes render per the strict color spec; Arabic RTL mirrors correctly (incl. any progress bars); all strings ICU, none concatenated. | 13 §3/§5; 05 (§ data-fetch/mutation rules, endpoint consumption sketch); 01 (i18n/RTL, Core Opinions); 04 §7 (endpoint conventions) |
| `S-<date>-X3-apple-NN` | apple | **HealthKit bridge (`HDSensors`)**: observer + anchored queries per type, background delivery; read Weight/Body Fat %/Heart Rate/Skin Temp on-device, serialize to JSON, push to the X3 ingest endpoint; **write-back** burned active calories + server-confirmed completed hydration (250 ml → dietary water sample) with idempotency-tagged sample metadata. Water % + Skin Temp mapping flagged, not blocking direct-mapped types. | `S-<date>-X3-backend-NN`; Apple suite **scaffold** + **iOS core-app** sessions (workspace, `HDAPIClient`, TOGO auth); **M1 hydration backend** (for completed-hydration write-back) | **Client-engine template + HealthKit:** reads bridged as JSON and pushed to the X3 endpoint; write-back writes **only** backend-accepted entries (calories + hydration), idempotency-tagged so retries do not duplicate samples; denied-permission paths are **non-fatal** (silently-empty types, no guessed error states); **thin-client/SSOT audit passes** (no on-device interpretation, thresholds, or protocol constants — the `250`/`5000`/`30` never embedded as rule values); both themes + Arabic RTL for any body-metrics UI; ICU strings. | 13 §3/§4/§5; 07 (§3 `HDSensors` layout, §3.1 sensor-vs-logic line, §5 HealthKit read/permission/write-back, §Open Questions); 01 (Core Opinions, RTL); 04 §7 |
| `S-<date>-X3-android-NN` | android | **Health Connect bridge (`core/healthconnect`)**: read Weight/Body Fat %/Heart Rate/Sleep on-device with **point-of-use permission groups**; push raw records to the X3 ingest endpoint via WorkManager + changes-token differential (deltas, not full history); **write-back** backend-accepted hydration (250 ml → Hydration record) and nutrition (mapping flagged) for ecosystem parity. | `S-<date>-X3-backend-NN`; Android suite **scaffold** + **core-app** sessions (`core/network`, TOGO auth token handling); **M1 hydration backend** (hydration write-back); nutrition write-back also needs **M4 trigger-families backend** (parity, flagged) | **Client-engine template + Health Connect:** read/push round-trip for Weight/Body Fat %/Heart Rate/Sleep; write-back fires **only on backend-accepted** logs — a 30s-cooldown- or 5000ml-cap-rejected hydration tap produces **no** Health Connect record; permission groups requested at point of use with non-fatal denied-permission fallbacks; **thin-client/SSOT audit passes** (no biometric interpretation, no protocol constants in client); both themes + Arabic RTL; ICU strings, none concatenated. | 13 §3/§4/§5; 08 (§4 Health Connect read/write/endpoint sketch, §Open Questions); 01 (Core Opinions, RTL); 04 §7 |

*Session count: 4 (backend, web, apple, android).*

## Notes & gotchas

- **Legacy precedents worth reusing.** The legacy build already had a `health_samples` table and a Zepp smart-scale flowing weight/body-fat/HR/sleep in through Apple Health — mine its shape and the **baseline-from-earliest-reading** progress idea (validate the greenfield module against any legacy `*_test.go` cases first, per doc 12). Keep the greenfield DB conventions (NOT NULL + default) and **re-evaluate** the legacy anti-patterns (text ids, absent-date-as-empty-text, triggers-as-CSV) rather than porting them.
- **Thin-client / SSOT is the liability to avoid.** The legacy iOS app carried a *second* protocol implementation (never called `/api/today`) — do not repeat it. X3 clients read the device store and **render server state only**; the baseline/progress math and any interpretation live exclusively in the backend module's `core/`. The Apple audit specifically greps for embedded rule numbers (`250`/`5000`/`30`/`90`/`240`/`60`/`3`).
- **Categorical, not quantitative + no invented certainty.** Biometrics are transported verbatim; there is **no** threshold logic, no "your heart rate is high" copy, and no diagnosis-shaped output on any surface. Any conclusion is backend behavioral-pattern analysis and always defers to "Consult your doctor."
- **PENDING DESIGN — open items (do not silently resolve):**
  - **Cross-scope reconciliation** of Apple vs Android read scopes: Apple lacks a Sleep read; Android lacks Body Water % and Skin Temp. The unified backend scope must accept partial per-platform coverage without erroring on an absent type.
  - **Body Water %** has **no first-party HealthKit type** — where it comes from on iOS (vendor-specific HealthKit writes? unsupported on Apple? backend-only via other clients) is unresolved.
  - **Skin Temp mapping** — `appleSleepingWristTemperature`, `bodyTemperature`, or both, and how the backend labels them on ingest.
  - **Nutrition write-back mapping (Android)** — Health Connect nutrition records are quantitative while Health Debug is categorical; what (if anything beyond name/timestamp) is written back is undecided (Health Debug holds no nutrient quantities).
  - **Offline / queuing behavior** for pushes (queue with original timestamps vs. reject vs. hybrid) is unresolved project-wide and interacts with server-enforced temporal rules.
  - **Ingest sync cadence** (background sync interval, changes-token differential cadence) is a backend-negotiated setting, unconfirmed.
  - **Legacy weigh-in window 07:00–11:00** is a legacy precedent, **not** a spec constant — do not adopt without ratification.
- **Ordering.** X3 backend must precede all X3 clients. The Apple/Android sessions also depend on their suite scaffold + core-app sessions and on **M1 hydration backend** for the hydration write-back path (Android nutrition parity additionally leans on M4). Sequence X3 alongside the Apple/Android engine builds, not before them.

## Handoff reminder

At session end, follow doc 13 §6: **retain** one `session-handoff` memory then **`memory/edit`** it to attach metadata (retain cannot set metadata). Use a **unique** `source_ref: handoff/X3-sensors-biometrics/<surface>/<n>` (never reuse — a duplicate supersedes the prior). Metadata: `{project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"X3", title:"Handoff <session-id>", tags:[...], next:"<module+surface>"}`. Fill the content with the exact SESSION HANDOFF structure (Module/Surface/Status, Summary, Files touched, Decisions, Deviations, Tests run, OPEN THREADS, NEXT SESSION). 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 open question also gets its own classified memory and, if it changes the plan, an edit to the relevant plan doc.
