# 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):**

```sql
-- Food ↔ trigger-family membership is categorical: a flag, not a measure.
-- (Column list is a sketch; the real schema lives in the data-model plan doc.)
food_trigger_membership (
  food_id          NOT NULL DEFAULT ...,
  trigger_family   NOT NULL DEFAULT ...,  -- enum: gout | ibs_gerd | fatty_liver
  is_trigger       NOT NULL DEFAULT false -- boolean; the ONLY evaluation dimension
)
-- Deliberately absent: portion_grams, serving_size, calories, quantity, dose.
```

**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 `NULL` out of its state table has an undefined state. Three-valued SQL logic (`NULL` comparisons) 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 NULL` schemas 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 NULL` with 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 NULL` attainable before the constraint is applied.

**How it is tested.**
- An automated schema test queries `information_schema.columns` on a migrated test database and asserts zero rows with `is_nullable = 'YES'` and zero rows with a missing `column_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-narrowing `ALTER`, 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 `ar` locale 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 `#1A1A1A` charcoal; `#00FF33` neon green glow for active timers; `#00CCFF` cyan for active buttons; `#FF3333` red for alerts; glowing effects.
- **Light Mode:** background `#FFFFFF`; `#20A060` saturated teal; `#0070A0` deep 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 `#20A060` vs `#0070A0` in 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:

1. **Key-encryption mechanism** for BYOK storage (KMS choice, rotation policy). Spec fixes only: encrypted in backend, never on client.
2. **Complete color token map** beyond the seven named hex values, including glow parameters and light-mode role assignments. PENDING DESIGN.
3. **RTL mirroring rules per component** (timer sweep direction, charts). PENDING DESIGN.
4. **Exact lint-rule implementations** per toolchain for the protocol-constant ban (C4) — the requirement is fixed; the rule authoring is an implementation-phase task.
5. **Supported-client-version window** for expand-contract contract phases (how old a client release must still be supported before contracting). Not specified.
6. **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.
7. **Locale list beyond Arabic + English.** Full Arabic RTL is required; no other locales are named in the spec.
