Páginas · Health Debug
X4 — Update Gate · Session Card
Per-module session list for X4 Update Gate. Read alongside
docs/plan/13-execution-modules-and-sessions.md(the framework),04-backend-togo.md(resource/migration/REST conventions),12-legacy-reference-map.md§5.7 (the proven legacy precedent), and the per-surface PRDs (05 web, 07 apple, 08 android, 09 desktop). A future build session opens this card to know exactly what to build for its cell.
Purpose
The Update Gate makes release management server-owned, not store-owned. A single app_releases resource plus a public, unauthenticated GET /api/app/version?platform=&build= endpoint lets the ToGO backend tell any client whether its installed build is up_to_date, update_available, or update_required — the verdict is computed server-side by comparing the client's reported build against the release manifest, never by the client. Admin CRUD plus a Releases admin page mean a new version is published without a code deploy or a psql session. Each client acts on the server verdict in the way its platform allows: Android drives Play In-App Updates (the download mechanism only), Apple deep-links to the App Store (no equivalent API exists), Electron uses electron-updater against the same manifest, and web shows an update banner. This is the module that gates the first store submission (doc 13 §4).
Engine / feature parameters (exact — do not invent)
This module has no protocol-engine constants (it is a backend resource + version-verdict service, not one of the 8 protocol engines). Its fixed contract, carried from the proven legacy pattern (GitHub issue #4, brain memory healthdebug-update-gate-contract):
- Resource
app_releasefields:platform,version,build,min_supported_build,store_url,release_notes_en,release_notes_ar,is_mandatory,released_at. - Public endpoint:
GET /api/app/version?platform=<>&build=<>→ returns one ofup_to_date | update_available | update_required, plusstore_url,release_notes_en/ar, andis_mandatory. - Verdict rule (server-computed SSOT): reported
build<min_supported_build⇒update_required; reportedbuild< latest publishedbuildfor the platform ⇒update_available; otherwiseup_to_date.is_mandatoryon the target release also forcesupdate_required. - Unauthenticated by necessity: the endpoint must answer for a client whose token was invalidated by the very change that requires the update — so it is on the deny-by-default guard's allowlist as public (no
Authorizationrequired). - Android uses Play In-App Updates (
AppUpdateManager) — IMMEDIATE flow for mandatory, FLEXIBLE otherwise — for the download only. Play's ownupdateAvailabilityis a fallback, not the source of truth (it cannot express "this build is protocol-incompatible"); the server manifest verdict decides. - Apple has no equivalent API — the update sheet deep-links to the App Store via
store_url. - Desktop uses electron-updater sharing the same manifest.
- Web shows an update banner.
Sessions
Surface order is backend → web → mcp → chrome → apple → android → desktop (doc 13 §4); backend must land before any client session. mcp and chrome get no session in this module: Chrome extensions auto-update through the Web Store and the MCP server/CLI updates through its package manager, so neither needs an in-app update gate (mirrors how M6/M7 skip chrome). Session-ID <date> and NN are placeholders filled at session start.
| Session ID | Surface | Scope (what this one session builds) | Entry deps | Done criteria (doc 13 §5) | Plan docs to read |
|---|---|---|---|---|---|
S-<date>-X4-backend-NN | backend | app_releases table (migration; every column NOT NULL with a default; strictly expand-contract) + sqlc queries. Server-side version-verdict service implementing the up_to_date/update_available/update_required rule. Public unauthenticated GET /api/app/version?platform=&build= on the guard allowlist, returning verdict + store_url + release_notes_en/ar + is_mandatory. Admin CRUD endpoints (authenticated, admin-scoped) so releases are published without a deploy. | M0.1–M0.4 (microkernel, TOGO auth for the admin endpoints, migration harness, error envelope) | Adapted Backend-engine template: migration is expand-contract and every column NOT NULL + default; the public endpoint returns server-computed verdicts; the verdict logic lives server-side and is covered by unit tests that encode all three outcomes + the min_supported_build / is_mandatory edges literally; integration test against real Postgres (unknown/lower/equal/higher build; admin-created release flips a client from up_to_date→update_required); admin endpoints reject non-admin (404-not-403 isolation); the version endpoint answers with no token. (plugin/core clause is N/A — this is a resource+service, not one of the 8 engines — but the verdict comparison is the SSOT and never leaves the server.) | 04, 13, 10 (admin auth), 12 §5.7 |
S-<date>-X4-web-NN | web | End-user update banner that renders the server verdict (dismissible for update_available, blocking for update_required) using release_notes_en/ar + store_url from the API. Plus the Releases admin page: a CRUD form over the admin API so a version is published from the browser (no deploy, no psql). | M0.5–M0.7 (app shell, theme engine, i18n), S-<date>-X4-backend-NN | Client-engine template: renders /api/app/version state and the admin API only; thin-client/SSOT audit passes — grep finds no min_supported_build/build comparison or mandatory logic in client code; both dark-glow and light-flat themes per the strict color spec; Arabic RTL mirrors correctly; banner + admin copy are ICU messages, release notes rendered as server strings (never concatenated). | 05, 13, 01 (colors/i18n), 12 |
S-<date>-X4-apple-NN | apple | Update sheet shown on the server verdict; for update_required it blocks and deep-links to the App Store via store_url, for update_available it offers an optional prompt. No local version math. | S-<date>-X4-backend-NN, Apple app shell (M1 apple) | Client-engine template: renders the server verdict and deep-links only; thin-client audit passes (no build comparison, no client-decided "mandatory"); both themes; Arabic RTL; ICU strings; release notes from release_notes_en/ar. | 07, 13, 02 (divergences), 12 |
S-<date>-X4-android-NN | android | Wire Play In-App Updates (AppUpdateManager): IMMEDIATE flow when the server verdict is update_required/is_mandatory, FLEXIBLE otherwise. Server manifest is the source of truth; Play updateAvailability is only a download fallback. | S-<date>-X4-backend-NN, Android app shell (M1 android) | Client-engine template: the decision to prompt/force comes from the server verdict, not from Play or client math; thin-client audit passes (grep finds no min_supported_build comparison); both themes; Arabic RTL; ICU strings; release notes from the API. | 08, 13, 02, 12 |
S-<date>-X4-desktop-NN | desktop | Configure electron-updater to consult the shared manifest / store_url feed and surface the server verdict (reuses the web banner components where possible); blocking behavior for update_required. | S-<date>-X4-backend-NN, Desktop Electron shell (M0.5 web + Electron scaffold) | Client-engine template: acts on the server verdict, no local version logic; thin-client audit passes; both themes; Arabic RTL; ICU strings. | 09, 13, 12 |
Notes & gotchas
- Legacy precedents worth reusing. The whole pattern is proven in the legacy tree (GitHub issue #4; brain memory
healthdebug-update-gate-contract,9d7ee1e2): legacy Android already shipped an update gate, and legacydesktop/already carriedelectron-updaterin its scaffold (doc 12 §3). Mine those for the wiring; carry no code forward (doc 12 header rule) — reference only what matches this plan. - Liability: never let the client decide "mandatory." The
is_mandatory/update_requiredverdict is server-owned. Android must treat PlayupdateAvailabilityas a fallback, not the truth, because Play cannot express "this build is protocol-incompatible." A client that computes its own mandatory state re-introduces the duplicated-logic liability the thin-client rule exists to prevent (doc 12 §6). - Liability: keep the version endpoint unauthenticated. It is deliberately on the deny-by-default allowlist. If it ever required a token it could not answer the exact clients (token invalidated by the update) it exists to serve. Do not add auth "for consistency."
- Do not hardcode store URLs in clients.
store_urlcomes from the manifest so a store move needs no client release. - PENDING DESIGN.
- Exact banner/sheet copy and layout and the Releases admin page UI (colors follow the strict spec; composition is a UI-phase decision).
- FLEXIBLE-update UX: snooze cadence / re-prompt interval for non-mandatory updates.
- Theme override behavior for the banner/sheet (tracks the M0.6 system-vs-manual override decision, still PENDING DESIGN).
- Build/version comparison edge policy to confirm at implementation:
buildis treated as a per-platform monotonic integer; the tie-break and cross-platform semantics ofversionvsbuildshould be recorded as an architecture-decision memory when fixed.
Handoff reminder
At session end, write one session-handoff memory to the healthdebug brain — retain the content in the doc 13 §6 structure, then memory/edit to attach metadata {category:"session-handoff", module:"X4", surface:"<surface>", status:"...", next:"<module+surface>", ...}, with a unique source_ref: handoff/X4-update-gate/<surface>/<n> (never reuse — a repeated source_ref supersedes the prior handoff). 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 (e.g. the build-comparison policy above) also gets its own classified memory and, if it changes the plan, an edit to the relevant plan doc. See doc 13 §6 and docs/brain/BRAIN-INDEX.md.