Seiten · Health Debug
M1 — Hydration (Reference Engine) · Session Card
Planning artifact. A build session opens this card (after recalling the brain per doc 13 §6) to know exactly what its one cell builds, what must exist first, and when it is done. This is the reference engine: it is built fully across every surface first, and every later engine (M2–M8) copies its shape. Get the pattern right here — clean plugin purity, a thin-client/SSOT audit that passes, expand-contract migrations, ICU/RTL from day one — and the remaining engines' per-surface sessions become mechanical.
1. Purpose
Hydration tracks water intake in fixed categorical 250 ml units and paces it with a server-enforced cooldown and a daily cap, so the user builds a steady hydration habit relevant to kidney health and gout prevention. The engine is deliberately simple so it can serve as the archetype for the whole grid: a pure core/ state machine registered as its own ToGO plugin (the SSOT for all cooldown/cap/day-boundary logic), fronted by REST endpoints, and rendered by seven thin clients that submit "log one unit" intents and draw the returned snapshot — never computing protocol logic locally. It embodies both Core Opinions: intake is categorical ("a unit of water was consumed," never a free-form ml quantity), and the engine invents no medical certainty (it never says "you are dehydrated"; any AI narrative over the history is pattern description ending in "Consult your doctor").
2. Engine parameters (exact — do not invent)
| Parameter | Value | Source of truth / notes |
|---|---|---|
| Unit size | 250 ml | The only loggable increment. Categorical unit of logging, not a quantity. |
| Daily maximum (cap) | 5000 ml (= 20 units) | Hard cap. Accumulation window (the "day") is an open question — see §4/Notes. |
| Entry cooldown | 30 seconds | Minimum gap between two accepted entries; enforced server-side, transactionally. |
| Idempotency | Idempotency-Key required on POST | Same user + same key ⇒ original result replayed, no duplicate row. |
| Storage unit | units smallint, default 1 | 1 unit = 250 ml; canonical storage is units, never free-form ml. |
| Default goal | 2500 ml | Legacy precedent (GlassMl=250, MaxDailyMl=5000, LogCooldown=30s, default goal 2500 ml). Goal, and its pacing, are PENDING ratification — not spec-fixed. |
| Goal pacing | Paced linearly across the WORK WINDOW, not the calendar day | Legacy precedent worth reusing; informs the day-boundary/anchor open question. PENDING DESIGN for the anchor source. |
source enum | web | ios | watchos | android | wearos | extension | desktop | Finalized when the clients exist; default 'unknown'. |
| Endpoints | POST /api/v1/hydration/entries · GET /api/v1/hydration/today · GET /api/v1/hydration/entries?date=... · (DELETE .../entries/{id} undo — window rules are an open question) | /today response shape is PENDING DESIGN (must carry whatever the ring/timer UI needs). |
Invariants: total is always a multiple of 250 and 0 ≤ total ≤ 5000; no two accepted entries for one user are < 30 s apart (server clock); cooldown and cap are checked only server-side; on any ambiguity (clock skew, duplicate suspicion) the engine declines to count the entry rather than inflating the total.
3. Session table
Surface order (doc 13 §3/§4): backend → web → mcp → chrome → apple → android → desktop. The backend session must land before any client session. Apple and android bundle several device targets plus a sensor write-back and are sliced into a handheld session and a wearable+sensor session (doc 13 §2/§3 authorize slicing a too-big cell); the NN placeholder sequences the two. Replace <date> (e.g. 2026-09-05) and NN (01, 02, …) when the session is opened.
| Session ID | Surface | Scope (what this one session builds) | Entry deps | Done criteria (doc 13 §5) | Plan docs to read |
|---|---|---|---|---|---|
S-<date>-M1-backend-NN | backend | Hydration ToGO plugin: pure core/ state machine (Idle/Cooldown/Capped) + plugin.go kernel registration; hydration_entries schema (id, user_id, units smallint dflt 1, logged_at = server-received time, source dflt 'unknown', idempotency_key dflt '' with unique (user_id, idempotency_key) for non-empty keys, created_at); the three endpoints; the POST transaction that enforces 30 s cooldown + 5000 ml cap under a per-user serialization (advisory lock or equivalent) so two concurrent posts cannot both pass; idempotency replay; engine unit tests encoding 250 / 5000 / 30 s literally; integration test on real Postgres. | M0.1–M0.4 (kernel, auth, plugin framework, migration harness + error envelope) | Plugin registered on the kernel; migration is expand-contract and every column is NOT NULL with a default; endpoints return computed state; engine unit tests encode every spec constant (250/5000/30) literally and pass; integration test against real Postgres passes (idempotency replay, cooldown/cap races, Arabic-locale localized rejection code); no protocol constant appears outside the engine's core/ package. | 03 §1; 04 §5–§9; 13 §5 |
S-<date>-M1-web-NN | web | Hydration dashboard card + ring on /engines/hydration, rendering GET /today snapshot only; a "+250 ml" log intent wired through the ['engine','hydration',date] query + log-water mutation; disabled/cooldown state driven purely by the snapshot. | M1 backend; M0.5 (app shell/auth), M0.6 (theme), M0.7 (i18n) | Surface renders /today state and submits log intents only; thin-client/SSOT audit passes (grep for 250/5000/30 as behavior finds nothing); both themes render per the color spec (dark neon-green #00FF33 glow / light flat teal #20A060); Arabic RTL mirrors correctly incl. the progress ring; all strings ICU, none concatenated. | 03 §1; 05; 13 §5 |
S-<date>-M1-mcp-NN | mcp | log_water tool (POST hydration entry, +250 ml, Idempotency-Key header) and today_status (GET today state, returned verbatim); CLI equivalents (healthdebug log water, healthdebug status). | M1 backend; M0.8 (MCP server/CLI skeleton — itself gated on M1 backend) | Each tool calls the real API with personal-access-token auth, returns server state verbatim, holds no protocol logic and no AI keys; the 30 s cooldown / 5000 ml cap remain the server's guards; excluded tools stay excluded (no complete_break). | 03 §1; 11; 04 §7; 13 §5 |
S-<date>-M1-chrome-NN | chrome | MV3 popup with a "+250 ml" button (log intent) and a toolbar badge showing hydration % derived from the /today snapshot; minimal permissions (storage/alarms + one host, no content scripts); PAT in chrome.storage.session. | M1 backend; M0.2 (PAT auth) | Renders /today and submits log intents only; thin-client/SSOT audit passes (no cooldown/cap math in extension code); both themes + Arabic RTL correct; ICU strings, none concatenated; badge value comes from the server snapshot, never computed locally. | 03 §1; 06; 13 §5 |
S-<date>-M1-apple-NN | apple (iOS handheld) | iOS hydration card rendering /today; "+250 ml" quick-log intent; PAT auth via TOGO. | M1 backend; M0.2 (PAT auth) | Renders /today and submits log intents only; thin-client/SSOT audit passes (Swift grep for 250/5000/30 as behavior finds nothing); both themes + Arabic RTL (mirrored ring) correct; ICU strings, none concatenated. | 03 §1; 07; 13 §5 |
S-<date>-M1-apple-NN | apple (watch + widget + HealthKit) | WidgetKit ring widget; watchOS quick-log complication/screen; write completed hydration back to HealthKit. | This module's apple iOS session; X3 sensors foundation (HealthKit permission + client plumbing) | Renders /today-derived state and submits log intents only; write-back pushes to HealthKit without any client-side cap/cooldown logic; thin-client/SSOT audit passes; themes + RTL correct on watch and widget; ICU strings. (If X3 HealthKit plumbing is not yet ready, the write-back is deferred and this session ends partial — see Notes.) | 03 §1; 07; X3; 13 §5 |
S-<date>-M1-android-NN | android (phone) | Phone hydration card rendering /today; "+250 ml" quick-log intent; Glance home-screen widget with the ring; PAT auth. | M1 backend; M0.2 (PAT auth) | Renders /today and submits log intents only; thin-client/SSOT audit passes (Kotlin grep for 250/5000/30 as behavior finds nothing); both themes + Arabic RTL (mirrored ring/widget) correct; ICU strings, none concatenated. | 03 §1; 08; 13 §5 |
S-<date>-M1-android-NN | android (Wear + Health Connect) | Wear Tile quick-log; write completed hydration back to Health Connect. | This module's android phone session; X3 sensors foundation (Health Connect permission + client plumbing) | Renders /today-derived state and submits log intents only; write-back pushes to Health Connect with no client-side protocol logic; thin-client/SSOT audit passes; themes + RTL correct on Tile; ICU strings. (If X3 Health Connect plumbing is not yet ready, write-back is deferred and this session ends partial.) | 03 §1; 08; X3; 13 §5 |
S-<date>-M1-desktop-NN | desktop | Electron tray quick-log with global shortcut Ctrl+Alt+W (+250 ml intent); reuses the web hydration card/ring via the shared packages/ui + packages/api-client; PAT in safeStorage. | M1 web (shared card); Electron shell that loads the web app (scaffolding precondition — see Notes) | Renders /today state (via reused web components) and submits log intents only; thin-client/SSOT audit passes (no protocol constants in Electron main/renderer glue); both themes + Arabic RTL inherited from the web card; ICU strings, none concatenated. | 03 §1; 09; 05; 13 §5 |
Session count: 9.
4. Notes & gotchas
- Legacy precedents worth reusing (candidates, not spec):
hydration.goshippedGlassMl=250,MaxDailyMl=5000,LogCooldown=30s— these match the fixed spec constants, so reuse them verbatim. It also had a default goal 2500 ml paced linearly across the WORK WINDOW (not the calendar day) and statuseson_track / slightly_behind (deficit ≤ 500) / dehydrated / goal_reached. The goal, its pacing, and those status bands are PENDING ratification (not spec-fixed) — carry them as the proposed default and flag any use. - Concurrency (the load-bearing test): the cooldown/cap read-then-insert must run under per-user serialization (transactional advisory lock or equivalent) so two simultaneous
POSTs cannot both pass the checks. The invariant (no double-accept) is fixed; the mechanism is an implementation choice. The cooldown-race integration test (two concurrent posts ⇒ exactly one accepted) is mandatory and is the pattern every later engine copies. - Idempotency: unique constraint on
(user_id, idempotency_key)for non-empty keys; a replayed key returns the original result and inserts no new row. Retention window is an implementation decision — document it with the endpoint. - Fail-safe: on any ambiguity (clock skew, suspected duplicate, late offline-watch sync) the engine declines to count rather than inflating totals; conflicting late entries are rejected or flagged, never silently merged.
- Thin-client/SSOT audit is a gate, not a suggestion: every client session must pass a grep proving no
250 / 5000 / 30(nor any cooldown/cap/window math) lives in client code. Display strings mentioning these numbers come from the backend snapshot or ICU messages interpolated from API data. - Desktop shortcut divergence: the greenfield tray quick-log uses Ctrl+Alt+W (per this module brief). Legacy Electron used
Ctrl/Cmd+Shift+W— do not copy the old binding. - HealthKit / Health Connect write-back depends on the X3 sensors foundation (permission groups + client plumbing). X3 runs alongside the apple/android engine sessions; if its plumbing is not ready when the wearable+sensor session runs, ship the render/quick-log and mark the write-back a partial/open thread rather than inventing local sensor logic.
PENDING DESIGN / open questions (do not invent answers)
- Day-boundary / cap-reset anchor + timezone/DST policy for the 5000 ml daily cap and the work-window goal pacing — an open question that must be settled before the schema freeze.
- Multi-unit per entry: whether one API entry may carry > 1 unit, or strictly one unit per entry (cooldown rate-limits logging). Spec is silent; depends on the UI logging control.
- Violation model: reject-at-API vs. accept-and-flag (the plan assumes accept-and-flag). Violation alert rendering is PENDING DESIGN.
- Visuals PENDING DESIGN: the
/todayresponse shape (must carry what the ring/timer needs); the cooldown/disabled-button neon-#00FF33-glow behavior; the hydration ring itself; refresh/animation cadence per surface (web, watch, badge, widget/Tile); Human Node status visual (PENDING DESIGN); history pagination/range shape. - Undo/edit window for a mistaken entry (
DELETE .../entries/{id}) and its effect on already-evaluated cap/cooldown state.
5. Handoff reminder
At the end of every session in this module, follow doc 13 §6: write one session-handoff memory to the healthdebug brain (retain the content in the §6 template, then memory/edit to attach metadata) with a unique source_ref: handoff/M1-hydration/<surface>/<n> and metadata { project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"M1", 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.