الصفحات · Health Debug
Core Opinions & Hard Constraints
Project: Health Debug (healthdebug.com) — greenfield build
Document status: Planning only. No implementation begins until UI design is finalized separately.
Reference: The older togo-based codebase at github.com/fadymondy/health-debug is reference-only and is never copied from.
Purpose
This document is the constitution of the Health Debug build. It states the two non-negotiable Core Opinions verbatim, unpacks what each one means in engineering terms, and then lists every hard constraint the project must obey — with, for each constraint, why it exists, how it is enforced (review checklists, CI checks, lint rules, schema validation), and how it is tested. Every later planning document (data model, protocol engines, API surface, client plans) is subordinate to this one. If a proposed feature or implementation detail conflicts with anything here, the feature changes — this document does not.
1. The Two Core Opinions (verbatim, non-negotiable)
Opinion 1 — The Protocol is Categorical, Not Quantitative. The system evaluates inputs by WHAT they are, not HOW MUCH (food is strictly 'Safe' or 'Trigger-bearing'). No complex quantitative AI deduction logic for food.
Opinion 2 — The App NEVER Invents Medical Certainty. The AI is strictly prohibited from diagnosing, prescribing, or guessing drug categories. Its sole job is behavioral pattern analysis. Final output must always defer to: 'Consult your doctor.'
Breaking either opinion breaks the app. They are not preferences; they are identity.
1.1 Opinion 1 unpacked — the categorical food model
What it means in engineering terms:
- Boolean safe/trigger per family. A food item's protocol status is a categorical membership question: is it Safe, or does it bear a trigger for one or more Trigger Families (Gout, IBS-GERD, Fatty Liver — per Engine 4)? The data model is a boolean/flag relationship between a food and each trigger family. Nothing more.
- No portion math. There is no portion size, serving weight, gram count, or "how much" dimension anywhere in the protocol evaluation path. A trigger-bearing food is trigger-bearing at any quantity.
- No calorie logic. No calorie fields, no calorie budgets, no macro tracking, no nutritional-density scoring. Calories do not exist in the protocol model.
- No quantitative AI deduction for food. The AI is never asked to estimate quantities, infer portion-adjusted risk, or compute dose-response reasoning about food. Food classification is a lookup against categorical data, not an inference.
- Quantities that DO exist are protocol mechanics, not food deduction. The spec defines exact fixed quantities inside specific engines — hydration in 250ml units with a 5000ml daily max and 30-second entry cooldown; the 90-minute caffeine block; the 4-hour GERD window; the 60-minute medication grace; the 3-cycle minimum for fertility prediction. These are hard-coded state-machine parameters of the Protocol Engines, not quantitative evaluation of food. The distinction: engines measure time and fixed units of behavior; food is only ever categorized.
Illustrative data sketch (planning artifact, not production code):
What is forbidden as a direct consequence:
| Forbidden | Reason |
|---|---|
quantity, portion, serving, calorie, dose columns in any food/protocol table | Reintroduces quantitative evaluation |
| AI prompts asking "how much X is safe" or "estimate the portion" | Quantitative AI deduction for food |
| Client UI collecting portion sizes for food logging | Data that must never influence evaluation should not be collected for it |
| "Moderation" / percentage-risk / scoring outputs for food | Food is binary per family: Safe or Trigger-bearing |
1.2 Opinion 2 unpacked — no invented medical certainty
What it means in engineering terms:
- AI output templates always end deferring to the doctor. Every AI-generated insight delivered to a user is rendered through a server-side output template whose closing element is the deferral: "Consult your doctor." The deferral is part of the template, not something the model is merely asked to include — so it cannot be omitted by model drift.
- No diagnosing. The AI never maps symptoms or patterns to a disease conclusion ("you have gout", "this looks like GERD"). It may only describe observed behavioral patterns in the user's own logged data.
- No prescribing. The AI never recommends starting, stopping, or changing a medication, dose, or treatment.
- No drug-category inference. The AI never guesses what category a logged medication belongs to, what it treats, or what it interacts with. Medication data is treated as opaque user-logged behavior (Engine 5, Medication Grace, tracks whether the user logged on time — never what the drug does).
- Behavioral pattern analysis only. The AI's entire permitted job: find patterns in the user's logged behavior and protocol-engine history (timing, adherence, correlations between logged events) and surface them descriptively.
- This applies to every AI provider equally. BYOK means OpenAI, Anthropic, Google, or Apple Intelligence may sit behind the proxy — the constraint is enforced in the server-side proxy layer, identically for all providers, precisely because the model itself can never be trusted to self-enforce.
Permitted vs. prohibited AI output (planning examples):
| Permitted (behavioral pattern) | Prohibited (medical certainty) |
|---|---|
| "You logged caffeine inside the 90-minute block on 4 of the last 7 mornings." | "Your fatigue is caused by adrenal issues." (diagnosis) |
| "GERD-window violations cluster on days you logged late meals." | "Take an antacid before bed." (prescription) |
| "Your medication log has fallen outside the grace window 3 times this week." | "That medication is a beta-blocker, so…" (drug-category inference) |
| …always followed by: "Consult your doctor." | Any output missing the deferral |
Future-state note: doctors prescribing 'Medical Restrictions' via the future Doctor dashboard is a human medical actor issuing instructions through the platform — that is compatible with Opinion 2. The prohibition is on the AI inventing certainty, not on licensed doctors using the system.
2. Hard-Constraints Table (summary)
| # | Constraint | One-line rule |
|---|---|---|
| C1 | NOT NULL + default on every column | Every column in every table is NOT NULL with a defined default. No exceptions. |
| C2 | Expand-contract migrations only | Every schema change follows the expand-contract pattern. No in-place breaking migrations. |
| C3 | Native-only clients, no hybrid | Web = React + TanStack; Apple = Swift; Google = Kotlin; Desktop = Electron (Windows/Linux); Chrome Extension = Manifest V3. No hybrid frameworks. |
| C4 | Server SSOT / thin clients | Clients never compute or hardcode protocol business logic. All temporal and state logic lives in the ToGO REST API. |
| C5 | BYOK server-side proxy | AI keys are encrypted in the backend; all AI calls are proxied server-side; no API key ever reaches a client. |
| C6 | ICU Message Format + full Arabic RTL | No string concatenation; ICU Message Format everywhere; full RTL including mirrored progress bars and layout. |
| C7 | Strict color spec | Exact hex values per mode; glow effects in dark mode, flat colors in light mode. |
Each constraint is detailed below with rationale, enforcement, and testing.
3. Constraint Details
C1 — Every column NOT NULL with a defined default
The rule. Every column in the PostgreSQL schema is declared NOT NULL and carries a defined default. Nullable columns are banned outright.
Why it exists.
- The 8 Protocol Engines are backend state machines; a state machine reading
NULLout of its state table has an undefined state. Three-valued SQL logic (NULLcomparisons) is exactly the class of silent bug that corrupts temporal logic like cooldowns, grace windows, and pre-sleep windows. - sqlc generates Go code from the schema; all-
NOT NULLschemas generate plain Go types instead of pointer/sql.Null*wrappers, eliminating an entire category of nil-dereference and forgotten-nil-check bugs in engine code. - "Absent" becomes an explicit modeled value (a default, a sentinel enum member, an empty string/array) that engineers must consciously choose, instead of an accidental
NULL.
How it is enforced.
- Schema validation (CI, blocking): a CI step runs against the Atlas-managed schema and fails the build if any column is nullable or lacks a default. Atlas schema linting / a schema-inspection script over the declarative schema is the gate; migrations cannot merge without it passing.
- sqlc as a tripwire: any generated Go struct containing a
sql.Null*or pointer scalar field indicates a nullable column slipped through; a CI grep over generated code fails on those types. - Code review checklist: every PR touching schema files must tick "all new/modified columns are
NOT NULLwith an explicit, documented default" and "the chosen default is a meaningful domain value, not a lazy zero." - Migration review: the expand phase of every expand-contract migration (C2) must show the backfill that makes
NOT NULLattainable before the constraint is applied.
How it is tested.
- An automated schema test queries
information_schema.columnson a migrated test database and asserts zero rows withis_nullable = 'YES'and zero rows with a missingcolumn_default(modulo identity/generated columns, which have defined generation rules). - Migration tests run every migration against a seeded database and assert the invariant still holds after each step, including mid-expand states.
C2 — Expand-contract migrations only
The rule. Every schema change is executed strictly as expand-contract: (expand) add the new column/table/index alongside the old, backfill, dual-write; (migrate) move readers to the new shape; (contract) remove the old shape only after nothing references it. No in-place renames, no drop-and-recreate, no single-step breaking changes.
Why it exists.
- Six client platforms (web, Apple suite, Android suite, Electron, Chrome extension) ship on independent release cadences — app-store review delays alone guarantee old client versions will be live against new schemas. Expand-contract keeps every deployed API version working during every migration.
- The Protocol Engines hold live temporal state (running cooldowns, open GERD windows, in-flight medication grace periods). A breaking migration mid-window could corrupt or reset user protocol state; expand-contract makes each phase individually safe and reversible.
- Atlas is the migration tool; it supports linting migrations for destructive operations, making the pattern mechanically checkable.
How it is enforced.
- CI checks (blocking): Atlas migration linting runs on every PR and fails on destructive operations (
DROP COLUMN,DROP TABLE, type-narrowingALTER, renames) unless the PR is explicitly labeled as a contract phase and links to the merged expand PR it contracts. - Code review checklist: schema PRs must declare their phase (expand / migrate / contract), and a contract PR must include evidence (link or query) that no code path or supported client version still reads the old shape.
- Process rule: expand and contract are never in the same PR or the same release.
How it is tested.
- A CI migration-compatibility test runs the previous release's API test suite against a database migrated one step ahead (post-expand), asserting old readers still pass.
- Roll-forward/roll-back tests execute each expand step, then the contract step, on seeded data — including seeded in-flight engine state (an open GERD window, an active hydration cooldown) — and assert engine state survives intact.
C3 — Native-only clients, no hybrid
The rule. Clients are exactly: Web = React + TanStack; Apple = native Swift (macOS, iOS, watchOS, CarPlay, Widgets); Google = native Kotlin (Android, WearOS, Android Auto, ChromeOS); Desktop = Electron (Windows, Linux); Chrome Extension = Manifest V3. No hybrid/cross-platform frameworks (no React Native, Flutter, Ionic, KMM-shared-UI, or web views posing as native apps).
Why it exists.
- IoT and biometrics are core, not accessory: Apple HealthKit and Google Health Connect are first-class native SDKs, and the Kinetic Pomodoro Engine depends on wearable movement detection (watchOS/WearOS) — capabilities hybrid layers wrap poorly, late, or not at all.
- Platform surfaces in scope (watchOS, CarPlay, Android Auto, Widgets, WearOS) are precisely the surfaces where hybrid frameworks are weakest or unsupported.
- Native clients keep the thin-client rule (C4) honest: each client is a presentation and sensor-collection layer, so platform-native UI toolkits are sufficient and appropriate.
How it is enforced.
- Repository/CI checks: per-client dependency manifests are scanned in CI; a denylist fails the build if hybrid-framework dependencies (React Native, Flutter, Cordova/Capacitor, etc.) appear in any client's lockfile/manifest.
- Code review checklist: new client dependencies require a checklist item confirming they are platform-native (or pure-JS for web/Electron/extension) and introduce no embedded cross-platform UI runtime.
- Architecture review: any proposal to share client code across platforms is limited to non-UI artifacts (e.g., generated API client types from the ToGO API spec) and must be approved against this constraint.
How it is tested.
- CI builds each client with its platform toolchain (Xcode for Swift targets, Gradle for Kotlin targets, Node toolchain for web/Electron/extension); a client that only builds via a hybrid toolchain cannot pass.
- Platform-integration smoke tests exercise the native-only capabilities each platform exists for: HealthKit read on iOS/watchOS, Health Connect read on Android/WearOS, Manifest V3 service-worker behavior for the extension.
C4 — Server is the Single Source of Truth; clients are thin
The rule. Clients NEVER compute or hardcode protocol business logic locally. All 8 Protocol Engines — every timer, threshold, window, cooldown, and categorical evaluation — live exclusively in the central ToGO REST API. Clients are presentation and sensor-collection layers: they render server state and push sensor/behavior data up.
Why it exists.
- The protocol numbers (250ml units, 5000ml cap, 30s cooldown, 90-minute caffeine block, 4-hour GERD window, 60-minute medication grace, 3-cycle fertility minimum) are medical-adjacent behavior rules. Six client codebases each reimplementing them guarantees drift — and drift here is a safety problem, not a cosmetic one (Opinion 2's credibility depends on the protocol being exactly one thing).
- Temporal logic (cooldowns, windows) computed on client clocks is wrong by construction: device clocks skew, time zones change mid-window, devices sleep. One server clock, one truth.
- The future state (doctors prescribing 'Medical Restrictions' that dynamically alter a user's Protocol Engines) only works if engines are server-side: a doctor's restriction must take effect without a client release.
How it is enforced.
- Lint rules (CI, blocking): protocol constants (90, 240 minutes/4 hours, 60, 250, 5000, 30, 3-cycle, the trigger-family names as logic, the GERD-window allowed list of water/chamomile/anise) are banned as decision-making literals in client code. A shared lint configuration per client toolchain (ESLint for web/Electron/extension, SwiftLint for Apple, ktlint/detekt for Android) flags protocol-domain constants and temporal comparison logic in client source. (Exact lint-rule design is an implementation-phase task; the requirement that such rules exist and block CI is fixed here.)
- Code review checklist: every client PR must tick "no protocol decision is made on-device; every displayed state/timer value originates from an API response." Client countdown displays may tick locally for rendering smoothness, but the authoritative deadline/state always comes from the server and the client re-syncs rather than deciding.
- API design rule: the ToGO API returns decisions and state (e.g., "entry rejected: cooldown active", "GERD window open until T"), not raw parameters for clients to evaluate. Endpoint plans in later documents must conform.
How it is tested.
- Contract tests: every client is tested against a mock ToGO API; tests flip server-side engine state (e.g., server says cooldown active) and assert the client renders the server's decision even when the client's local clock would disagree.
- Clock-skew tests: clients under simulated clock skew and timezone changes must still display server-authoritative state.
- Negative tests: a client built with the API mocked to be unreachable must show unavailable/stale state — never a locally computed protocol verdict.
C5 — BYOK AI keys: encrypted server-side, all AI calls proxied, no key ever on a client
The rule. Users bring their own AI keys (OpenAI, Anthropic, Google, Apple Intelligence). Keys are encrypted at rest in the backend. Every AI call is made server-side through the ToGO proxy. No API key, in any form, ever reaches any client.
Why it exists.
- Client-held keys are extractable keys — from a Chrome extension bundle, an Electron ASAR, or a decompiled APK. BYOK makes the key the user's asset and cost center; leaking it is leaking the user's money and account.
- Opinion 2 is enforced in the proxy: server-side templating (the mandatory "Consult your doctor." deferral), the behavioral-analysis-only prompt boundary, and the ban on diagnosis/prescription/drug-category inference are all applied where the call is made. If clients could call providers directly, the medical-safety layer would be bypassable.
- One proxy gives uniform treatment across all four providers.
How it is enforced.
- Architecture: the only credentials a client ever holds are its own TOGO auth identity tokens. AI endpoints on the ToGO API accept the user's request, load and decrypt the user's stored key server-side, call the provider, apply Opinion-2 output templating, and return only the templated result.
- Code review checklist: any PR touching AI endpoints must confirm no response schema, log line, error message, or debug payload can carry key material; any client PR adding an AI-provider SDK or a direct provider-domain network call is rejected.
- CI checks: client codebases are scanned for AI-provider SDK dependencies and provider API hostnames; secret-scanning runs on all repos; backend response schemas for AI endpoints are validated to exclude key-shaped fields.
- Encryption: keys are stored encrypted in the backend. (The specific encryption mechanism/KMS choice is not defined in the spec — to be decided in the security-design document; the requirement that keys are encrypted at rest and never leave the backend is fixed here.)
How it is tested.
- Egress tests: automated client tests run each client (extension, web, Electron; instrumented mobile builds) against a network monitor and assert zero connections to AI-provider hosts and zero key material in any outbound request.
- API tests: backend tests submit a key, then exercise every AI endpoint and every error path, asserting the key (and any decryptable form of it) never appears in responses or logs.
- Template tests: every AI proxy response in tests is asserted to terminate with the "Consult your doctor." deferral and to contain no diagnosis/prescription/drug-category content patterns (evaluated against a prohibited-output test suite per Opinion 2).
C6 — ICU Message Format, no string concatenation, full Arabic RTL
The rule. All user-facing strings use ICU Message Format. String concatenation for user-facing text is banned. Arabic is fully supported with complete RTL treatment — including mirrored layout and mirrored progress bars.
Why it exists.
- Concatenated strings break under grammar differences and are unlocalizable; ICU Message Format handles plurals, gender, and interpolation correctly per locale — essential for a data-heavy app full of counts, timers, and units (ml, minutes, cycles).
- Arabic RTL is a first-class market commitment, not a fallback: half-mirrored UIs (LTR progress bars in an RTL layout) read as broken and erode the trust a health product depends on.
How it is enforced.
- Lint rules (CI, blocking): per-client linters ban string concatenation/interpolation of user-facing text outside the i18n layer (ESLint rules for JS/TS surfaces, SwiftLint/ktlint equivalents for mobile) and require every rendered string to resolve through an ICU message catalog.
- Catalog validation (CI): message catalogs are parsed and validated as well-formed ICU on every PR; a check fails if a key exists in the source locale but is malformed or missing placeholders in others.
- Code review checklist: every UI PR ticks "no concatenated user-facing strings; new strings added to the ICU catalog; layout verified in RTL" — including directional components (progress bars, timers, charts, navigation).
- PENDING DESIGN: the definitive list of which visual components mirror in RTL versus stay direction-neutral (e.g., circular timer sweep direction, chart axes) depends on the finalized UI design and must be specified in the design handoff.
How it is tested.
- Automated: snapshot tests render key screens in
arlocale with RTL forced, diffed against approved RTL baselines (baselines are PENDING DESIGN until the visual design is final); unit tests exercise ICU plural/argument cases per locale; pseudo-localization builds catch hardcoded strings. - Directional-component tests: progress bars and any linear indicators are asserted to fill right-to-left under
ar. - Manual QA: an Arabic-locale review pass on each platform before each release covering layout mirroring, text expansion, and mixed-direction content (Arabic text containing Latin numerals/units).
C7 — Strict color spec
The rule. The exact palette is fixed:
- Dark Mode: background
#1A1A1Acharcoal;#00FF33neon green glow for active timers;#00CCFFcyan for active buttons;#FF3333red for alerts; glowing effects. - Light Mode: background
#FFFFFF;#20A060saturated teal;#0070A0deep cyan blue; flat colors (no glow).
Why it exists.
- The palette is brand identity and functional signaling in one: glow = active/attention in dark mode is a core interaction language (e.g., a glowing neon-green active timer), and alert red must be unmistakable and consistent across six client platforms. Per-platform color drift would fragment that language.
How it is enforced.
- Design tokens as the single source: the palette lives in one canonical token definition consumed by every client (web/Electron/extension CSS variables, Swift asset catalogs / theme constants, Kotlin theme resources — generated from the canonical tokens, not retyped). Raw hex literals outside the token layer are banned.
- Lint rules (CI): per-client lint checks flag hardcoded hex color literals in UI code outside the generated token files.
- Code review checklist: UI PRs tick "all colors come from theme tokens; dark mode uses glow treatment, light mode uses flat treatment, per spec."
- PENDING DESIGN: the complete token map is larger than the spec's named values — secondary/neutral shades, glow radii/intensity values, disabled/hover states, and exact role assignments for
#20A060vs#0070A0in light mode are not defined in the spec and must come from the finalized Figma design. Until then, only the named values above are fixed; nothing else may be invented.
How it is tested.
- Snapshot tests: themed screens are snapshot-tested in both modes and diffed for exact token values; a test asserts the rendered background, timer, button, and alert colors match the specified hex values.
- Token-sync test (CI): a check verifies every client's generated theme artifacts match the canonical token file byte-for-value.
- Visual QA: dark-mode glow rendering (which is GPU/compositor-dependent per platform) gets a manual visual pass per platform per release. Glow acceptance criteria are PENDING DESIGN.
4. Cross-cutting enforcement summary
| Mechanism | Covers | Gate |
|---|---|---|
Atlas schema lint + information_schema assertions | C1, C2 | CI, blocking |
sqlc generated-type scan (no sql.Null* / pointer scalars) | C1 | CI, blocking |
| Migration phase labeling + prior-release compatibility suite | C2 | CI + review, blocking |
| Client dependency denylist (hybrid frameworks, AI SDKs) | C3, C5 | CI, blocking |
| Protocol-constant / temporal-logic lint in client code | C4, Opinion 1 | CI, blocking |
| AI proxy output templating + prohibited-output test suite | Opinion 2, C5 | CI + backend tests, blocking |
| Egress monitoring tests (no provider hosts, no key material) | C5 | CI, blocking |
| ICU catalog validation + concat lint + RTL snapshots | C6 | CI, blocking (RTL baselines PENDING DESIGN) |
| Canonical design tokens + hex-literal lint + snapshot diffs | C7 | CI, blocking (full token map PENDING DESIGN) |
| PR checklists (schema, client, UI, AI-endpoint variants) | All | Human review, blocking |
5. Explicitly undefined (do not invent)
These are genuinely not specified in the current spec. They must be resolved by the user or by the pending design work — not assumed:
- Key-encryption mechanism for BYOK storage (KMS choice, rotation policy). Spec fixes only: encrypted in backend, never on client.
- Complete color token map beyond the seven named hex values, including glow parameters and light-mode role assignments. PENDING DESIGN.
- RTL mirroring rules per component (timer sweep direction, charts). PENDING DESIGN.
- Exact lint-rule implementations per toolchain for the protocol-constant ban (C4) — the requirement is fixed; the rule authoring is an implementation-phase task.
- Supported-client-version window for expand-contract contract phases (how old a client release must still be supported before contracting). Not specified.
- The full food/trigger-family catalog contents (which foods belong to which family). The categorical model is fixed by Opinion 1; the catalog data source is not specified.
- Locale list beyond Arabic + English. Full Arabic RTL is required; no other locales are named in the spec.