الصفحات · Health Debug
X5 — Release & Packaging — Session Card
Module X5 in the (module × surface) grid of
docs/plan/13-execution-modules-and-sessions.md. Read this card alongside doc 13 (the framework),03-protocol-engines.md,04-backend-togo.md, and the relevant platform PRD (05 web, 06 chrome, 07 apple, 08 android, 09 desktop, 11 mcp).
Purpose
X5 ships Health Debug to the world: production deploy of the ToGO backend + built React SPA, and store/packaging submission of every client (Chrome Web Store, App Store Connect, Google Play Console, electron-builder Windows/Linux). It builds no protocol code — it packages, signs, deploys, and lists what the engine modules (M0–M8) and cross-cutting modules (X1 notifications, X2 AI proxy, X3 sensors, X4 update-gate) already produced. Because the backend is the Single Source of Truth, this module never smuggles timer/cooldown/window/trigger logic into any shipped binary; a packaged client remains a thin renderer of server state. X5 is last per platform: a platform's release session runs only after that platform's engines and its X-modules are done. Per doc 13 §4, the backend/web production deploy must precede every client store submission (each client points at the live production API origin), and the X4 update-gate must exist before the first store submission.
Relevant fixed parameters and identifiers (exact — do not invent)
These are the load-bearing constants for packaging/deploy. Engine numeric constants are listed only to state the thin-client boundary: they live server-side and must never appear inside a store binary.
Identity / app ids
- Android
applicationId:io.threex1.healthdebug - Apple bundle id:
io.threex1.HealthDebug(+.widgets/.mac/.watchkitapp) — note the case differs from Android on purpose - Electron
appId:com.fadymondy.healthdebug
Signing / release secrets (values in the cabrain healthdebug secrets vault — secret_reveal, never commit)
- Android release keystore:
.secrets/healthdebug-release.jks, aliashealthdebug; password secretlegacy-android-keystore-password. LOSING THIS FILE MEANS A BRAND-NEW PLAY LISTING — confirm the vault/backup copy before signing. - Apple ASC API: secrets
apple-asc-api-key-id,apple-asc-issuer-id,apple-developer-team-id; private keyAuthKey_Z6H456ND57.p8(on the MacBook at~/.appstoreconnect/private_keys, outside the repo). - Electron publish: no
GH_TOKENstored yet — must be provisioned beforeelectron-buildercan publish to GitHub releases (fadymondy/health-debug).
Hosting topology (production deploy) — Cloudflare (proxied A record → origin 45.129.183.99) → Nginx Proxy Manager (Proxmox LXC 100, proxy host id 22) → SSH reverse tunnel → local host 127.0.0.1:18092; production PostgreSQL 16 local on :55437. One image/process serves both /api and the built SPA (index.html fallback, excluding /api, /graphql, /events).
Update gate (X4, consumed by every client here) — public unauthenticated GET /api/app/version?platform=&build= → up_to_date | update_available | update_required; server-owned app_releases is the SSOT (not the stores' own update signals).
Non-negotiable rules that gate this module
- SECURITY: never ship
ALLOW_DEV_LOGIN=1(the legacy prod shipped a passwordless admin backdoor at/api/auth/dev/login). The greenfield has no auth-dev plugin at all — verify absence, do not merely gate onAPP_ENV. - Every PostgreSQL column NOT NULL with a defined default; migrations strictly expand-contract (verified live before the deploy is "done").
- Auth: session cookie for web, personal access tokens for native/mcp/desktop/extension (TOGO auth).
- i18n: ICU Message Format, en/ar, full Arabic RTL (mirrored progress bars), no string concatenation — applies to store
_localesand listing copy. - Color spec for listing assets — Dark
#1A1A1Abg /#00FF33neon-green glow /#00CCFFcyan /#FF3333red (glow); Light#FFFFFFbg /#20A060teal /#0070A0deep cyan (flat). (Light-mode alert color is PENDING DESIGN.) - Thin-client boundary (server-side only, never packaged into a client): Hydration 250ml units / max 5000ml/day / 30s cooldown; Caffeine Block 90 min post-wakeup; GERD 4-hour pre-sleep window (water, chamomile, anise only); Medication Grace 60 min; Cycle requires 3 consecutive cycles; Kinetic Pomodoro break registers only on wearable-detected movement; Pomodoro 25/5/15, long break every 4, daily target 8 (PENDING ratification, not spec-fixed).
Session table
Session IDs follow S-<date>-X5-<surface>-NN (leave <date> and NN as placeholders until the session actually runs). Surface order per doc 13: backend → web → mcp → chrome → apple → android → desktop; the production deploy fuses backend+web into one infra session that gates all client submissions. There is no store/packaging session for mcp — the MCP server + CLI rides with the backend deploy (it is not a store artifact); see Notes.
| Session ID | Surface | Scope (what this one session builds) | Entry deps (done first) | Done criteria | Plan docs to read |
|---|---|---|---|---|---|
S-<date>-X5-infra-NN | backend + web (infra) | Production deploy of the ToGO API + built React SPA through Cloudflare → NPM (proxy host 22) → SSH reverse tunnel → 127.0.0.1:18092, single process serving /api + SPA. Point prod at the prod PostgreSQL (not the dev DB), TOGO auth (cookie for web, PATs for native), expand-contract schema applied, /healthz green through the tunnel, public GET /api/app/version reachable unauthenticated. Fix the canonical production API origin (the value every client below points at). Run + verify backups. | M0.1–M0.7 (foundation); all backend engine sessions M1–M8 + X1/X2/X3 backend + X4 update-gate backend; all web engine sessions (each web engine card done). | (Foundation template, doc 13 §5) API+SPA serve end-to-end through the full topology; /healthz and GET /api/app/version verified live; documented manual smoke check (cookie login, one log intent, /today renders). Security gate in place: grep + live probe confirm no dev-login route (/api/auth/dev/login → 404) and ALLOW_DEV_LOGIN is unset/absent. Schema-lint CI gate confirms every column NOT NULL+default and migrations expand-contract. Backups run; the offsite-backup-credential gap logged PENDING. | 04, 05, 10, 13 §5/§6; ops-deployment memories 5b21ff07, 611a0896, dd1328d2, 758cfd29; secret-inventory 17712456, 97145768. |
S-<date>-X5-chrome-NN | chrome | Package the MV3 extension; build the Chrome Web Store listing with _locales/en + _locales/ar (RTL-correct message catalogs), minimal permissions (storage/alarms/notifications + a single host_permissions = the finalized prod API origin; no content_scripts), privacy disclosures, listing assets (PENDING DESIGN), and submit for review. | S-<date>-X5-infra-NN (prod origin fixed); all chrome engine sessions (M1–M5 chrome) + relevant X-modules; X4 update-gate. | (Foundation template + client audit) Package uploads and clears Web Store review prerequisites; thin-client/SSOT audit passes (grep finds no protocol constants in extension code); both en/ar locales present and RTL renders correctly; documented manual check of popup/badge against prod API; permission-audit gate in place (no content scripts, single host). | 06 (§10 store readiness), 13 §5/§6; secret-inventory 17712456. |
S-<date>-X5-apple-NN | apple | Archive, sign, and submit to App Store Connect the Apple surfaces (iOS + macOS + watchOS + WidgetKit + CarPlay), bundle id io.threex1.HealthDebug (+ .widgets/.mac/.watchkitapp). Use the ASC API key (apple-asc-api-key-id / apple-asc-issuer-id / apple-developer-team-id from the vault; AuthKey_Z6H456ND57.p8 on the MacBook). WORKAROUND: the legacy Apple Development cert lost its private key, so xcodebuild -allowProvisioningUpdates cannot finish signing — mint the signing cert via the ASC API (the asc.py precedent), not local provisioning. App Store deep-link update sheet (X4) wired; watch motion validation for Kinetic Pomodoro functional. | S-<date>-X5-infra-NN; all apple engine sessions incl. M8 apple watch motion sub-session (mandatory — a break must be wearable-validated); X3 HealthKit; X1 APNs; X4 update-gate. | (Foundation template) Signed archives for each Apple surface upload to ASC via the API-minted cert; TestFlight/submission build accepted; documented manual check against prod API; thin-client audit re-verified; the cert-via-ASC-API workaround documented in the handoff; all secrets pulled from the vault, none committed. | 07, 10, 13 §5/§6; secret-inventory 17712456, 97145768. |
S-<date>-X5-android-NN | android | Build a signed release AAB (applicationId io.threex1.healthdebug), signed with .secrets/healthdebug-release.jks (alias healthdebug, password from vault legacy-android-keystore-password), and submit to Play Console (internal testing track at minimum). Wire Play In-App Updates (AppUpdateManager, IMMEDIATE for mandatory / FLEXIBLE otherwise) as the downloader only — the X4 app_releases manifest stays the SSOT. WearOS motion validation for Kinetic Pomodoro functional. | S-<date>-X5-infra-NN; all android engine sessions incl. M8 android Wear motion sub-session; X3 Health Connect; X1 FCM; X4 update-gate. | (Foundation template) Signed AAB built with the release keystore and uploaded to Play Console; keystore provenance verified (jks + password from vault, backup/offsite copy confirmed before signing); documented manual check against prod API; thin-client audit re-verified; en/ar string-parity gate passes. | 08, 13 §5/§6; secret-inventory 17712456. |
S-<date>-X5-desktop-NN | desktop | electron-builder producing signed Windows + Linux artifacts, appId com.fadymondy.healthdebug, publishing to GitHub releases (fadymondy/health-debug). electron-updater consumes the same X4 app_releases manifest. GAP: no GH_TOKEN stored yet — provision it before publish. Code-signing identities (Windows Authenticode; Linux package signing) are PENDING credential/design. | S-<date>-X5-infra-NN (prod API + web build the desktop shell reuses); all desktop engine sessions; X4 update-gate. | (Foundation template) Win + Linux artifacts build via electron-builder and (once GH_TOKEN is provisioned) publish; electron-updater points at the X4 manifest; documented manual launch check against prod API; theme/i18n/thin-client parity with web re-verified; the GH_TOKEN provisioning + code-signing-identity gaps resolved or logged PENDING. | 09 (§12 packaging / M6), 13 §5/§6; secret-inventory 17712456, 97145768. |
(A release-session done state maps to doc 13 §5's Foundation-session template — "its specific deliverable works end-to-end and is covered by a test or a documented manual check, and the CI/exit gate for its concern is in place" — since these are ops/packaging sessions, not engine sessions. Each client session additionally re-verifies the client-engine thin-client/SSOT + theme + RTL audit before shipping.)
Notes & gotchas
Security (highest priority)
- The legacy prod shipped
ALLOW_DEV_LOGIN=1with[email protected]— a passwordless admin backdoor at/api/auth/dev/loginon the live public site. Never reproduce this. The greenfield removed the auth-dev plugin outright rather than trust anAPP_ENVgate (precedent memory223b2069); confirm the route 404s in production before calling the deploy done.
Deploy liabilities carried from legacy (avoid)
- Prod-ran-against-the-dev-DB trap: legacy
run-host.ps1sourced.envand served the live site against the dev database. Point production explicitly at the prod PostgreSQL (:55437) and verify. - Supervisor foot-guns (memory
758cfd29): a scheduled task starts inC:\Windows\System32, where togo can't findtogo.yamlsearching upward (process starts, never binds, logs nothing) — set the working dir.& api.exe *>> $logkills the supervisor because PowerShell wraps a native command's stderr in a terminatingErrorRecord, and the server logs "listening on :18092" to stderr; useStart-Processwith file redirection instead. Run the tunnel task AT LOGON, not as SYSTEM, so it can read~/.ssh/cabrain_tunnelfrom the user profile. - DNS 522 trap: the wildcard
*.fadymondy.comA record points at a dead host; every new subdomain needs its own explicit A record at45.129.183.99or it returns 522 through Cloudflare. - Two divergent legacy DB copies (local PG16
:55437vs wedged Docker:55436) exist in the legacy tree — irrelevant to greenfield data but a reminder to confirm which DB the greenfield prod points at.
Reusable precedents
- Apple signing: cert lost its private key → mint via the ASC API (
asc.pyprecedent). Use the ASC key ids from the vault. - Update gate: server-owned
app_releases+ unauthenticatedGET /api/app/versionis a proven pattern (X4). Store update signals (PlayupdateAvailability, App Store, electron-updater) are downloaders/fallbacks — they can't express "this build is protocol-incompatible," so the server manifest stays SSOT. - NPM origin has no TLS cert (Cloudflare terminates TLS; NPM hosts use
certificate_id=0,ssl_forced=0). NPM does not regenerate nginx confs from its DB at startup — only via its own API.
PENDING DESIGN / PENDING credential
- Store listing assets (Web Store, App Store, Play graphics/screenshots, icons) — PENDING DESIGN.
- Light-mode alert color — PENDING DESIGN (affects any alert-colored listing asset).
- Electron
GH_TOKEN— not stored; provision before desktop publish. Windows/Linux code-signing identities and the auto-update format — PENDING. - Offsite backup needs a NEW credential — backups are still same-host on
E:; the SSH reverse-tunnel key iscommand=/bin/falserestricted and cannot be reused for rclone/rsync/NAS. Log as PENDING, do not block the deploy on it. - Greenfield deploy shape (Docker prod-compose vs bare-metal
api.exe) — legacy ran bare-metal on:18092; whether the greenfield prod uses the Dockerhealthdebug-prodcompose stack or bare-metal is a deploy decision to confirm in the infra session. - MCP distribution: the MCP server + CLI is not a store artifact — it ships with the backend deploy (auth via PAT). If a standalone CLI package (npm/binary) is later wanted, add it as a follow-up X5 session; it is out of scope for the five sessions above.
Handoff reminder
At the end of every X5 session, per doc 13 §6: write one session-handoff memory to the cabrain healthdebug brain — retain the content in the doc 13 §6 format, then memory/edit the returned id to attach metadata {project:"healthdebug", category:"session-handoff", era:"greenfield", status:"current", surface:"<surface>", module:"X5", title:"Handoff <session-id>", next:"<module+surface>"}, with a unique source_ref = handoff/X5-release/<surface>/<n> (never reuse). 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.