Files
Arah2/.planning/phases/03-mini-app-shell-geolocation/03-DEVICE-MATRIX.md
T

235 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 03 — Device-Matrix Exit Gate (ROADMAP Success Criterion 5)
**Deployed origin (staging):** https://araharah-bot.araharah-bot.workers.dev/
**Deployed version:** 2c1336e3-58f9-4b0b-86a8-b25de66e327c (via `npm run deploy` — theme → web → wrangler deploy with `web/dist` as static assets, D-13/D-14/D-15)
**Status: PENDING REAL-DEVICE VERIFICATION — the phase does not close until every leg has an explicit verdict.**
## Automated half (already green)
- `npm test -w web` — 33 passed, 0 failed (includes BA1–BA6 build-artifact gates over `web/dist`, HTML included)
- `npm test -w bot` — 48 passed, 0 failed
- `npm run capture -w web` — CAPTURE OK, 7/7 non-blank captures off `web/dist-harness`,
including the Desktop denied card, the `?pin=1` chat-pin relay, light + dark live shots,
and the DSGN-02 side-by-side. GEO-03 runtime probe: localStorage / sessionStorage /
cookies / IndexedDB all empty after a real boot-to-live cycle.
- Live canaries at the deployed origin: `GET /` → 200 text/html from the asset binding;
`POST /api/session` → 401 for unsigned bodies (the HMAC gate is live);
`getWebhookInfo` URL ends `/telegram/webhook` with no `last_error`;
`getChatMenuButton` type = `commands`.
## Bot API 10.2 origin prerequisite (hard, blocking)
`wrangler.jsonc`'s `run_worker_first` key drew an "Unexpected fields" warning from wrangler
4.151.3, but the deployed behavior was verified request-by-request: assets serve non-API
paths directly, `/api/*` and `/telegram/*` are answered by the Worker code (401/500, not
asset fallback) — D-13 routing holds in practice; the warnings are cosmetic at this wrangler
version.
- [ ] **BotFather Mini App URL == the deployed origin above.** Bot API 10.2 rejects Mini App
calls from any other origin silently — a mismatch means `Telegram.WebApp.*` no-ops with no
error. Check menu button (`/setmenubutton`) / WebApp URL in @BotFather
(`arasarah-bot.araharah-bot.workers.dev`), not a localhost or Pages URL.
Observed / Symptom: ______________________________________
## The six device legs (each: an explicit verdict + observation)
### 1. Android — real phone, Telegram Android (GEO-01)
- [ ] Boot: BOOT-01 (validating session) → BOOT-02 (acquiring location) → LIVE; the position
marker renders on the Rams basemap.
- [ ] Permission persistence: close the app fully, reopen — **no second permission prompt**.
The grant lives in Telegram's per-bot store on the device, outside the WebView.
Observed / Symptom: ______________________________________
### 2. Android stale-fix check (retires or confirms assumption A1)
- [ ] With the app LIVE, walk several metres and re-check. If the pin does not move, toggle
the phone's Location setting once (off → on) and record whether `navigator.geolocation`
then delivered a fresh fix, and whether the FIRST Android verification needed that toggle.
Result decides: **A1 stands** (toggle required) or **A1 retired** (no toggle needed).
Observed / Symptom (needed toggle? y/n): ______________________________________
### 3. iOS — real phone, Telegram iOS (if available)
- [ ] Boot to LIVE; marker renders; permission grant persists across a reopen.
Observed / Symptom: ______________________________________
(If no iOS device is available: record `UNVERIFIED — no device`; Success Criterion 5
stays open. Do not close the phase anyway.)
### 4. Desktop Telegram (GEO-02 end-to-end)
- [ ] Geolocation is absent by design (issue #77): the app shows the **denied card with
identical copy across all clients** (D-06, RESEARCH Pitfall 4) — no client-specific variant.
- [ ] Tap ASK VIA CHAT → POSTs `/api/location-fallback`; the location keyboard appears in the
user's own chat; tap it, share a live pin; the app picks it up via `GET /api/location`
(within its poll window) and reaches **LIVE**. This is GEO-02 end-to-end.
Observed / Symptom: ______________________________________
### 5. Plain browser (no Telegram context)
- [ ] Open the deployed URL directly in a desktop browser. Expected: the **denied card**
renders over the Rams shell (no `initData` → the HMAC session gate 401s), NOT a blank
page, NOT the live branch. A blank page is a FAIL.
(Optional extra observation: if a browser geolocation prompt leads to LIVE, note it —
but the pass condition for this leg is the deliberate denied state.)
Observed / Symptom: ______________________________________
### 6. Theme flip mid-session (PX-02 — manual-only verification)
- [ ] With the app LIVE on a real client, switch the Telegram theme **light ↔ dark
mid-session** without closing the app. Confirm: (a) shell chrome re-slots (document
background, shell ink swap); (b) the status strip stays matte-black; (c) **the map is
unchanged** — same style, no flash, no camera jump (D-10 / RESEARCH Pitfall 7).
Record a before/after screenshot in each direction and name the client used
(this leg can be folded into any of legs 1/3/4).
Verdict (chrome re-slotted? strip stayed black? map unchanged?): ______________________________________
## How to report back
- Type **"device matrix passed"** if all six legs reached LIVE or the documented
denied-and-recovered state (the theme-flip leg counts as passed when the chrome
re-slotted mid-session and the map was visibly unchanged).
- Otherwise report which leg failed and the exact symptom — a `/gsd-debug` gets opened for it.
- If no physical Android/iOS device is available: report that. The honest outcome is
Success Criterion 5 = UNVERIFIED; the phase is held open with the blocker recorded and
either a device acquired or the matrix explicitly deferred to Phase 7 hardening with
GEO-01/GEO-02 noted as unverified on real hardware. Never close the phase claiming
device verification that did not happen.
## Local-dev note (D-12 / assumption A3)
`localhost:8787` (via root `npm run dev` → `wrangler dev --port 8787 --local`) is a secure
context, so `navigator.geolocation` works under `wrangler dev` while a LAN-IP dev URL would
not. Remote staging (the deployed origin above) is used sparingly, immediately before a
real-device verification session, for exactly that reason.
## 2026-10-23 debug round 1 (mid-gate failure fixes)
**Reported failures + fixes (commit `b1d8768`, deploy `a21c0816`):**
1. *No webview entry from the bot* — root cause: phase 2 armed the menu button as
type `commands` and phase 3 never armed a web_app entry. Fix: new
`bot/scripts/set-menu-button.mjs` → `setChatMenuButton` type `web_app`,
url = deployed origin (verified live via `getChatMenuButton`).
⚠ Client-cached: the menu surface needs an app restart / `/`-autocomplete
to refresh (same quirk as phase 2).
2. *TRY AGAIN / ASK VIA CHAT dead on one card* — root cause: only the first
denied-card instance had listeners; the `awaiting-pin` variant is a second
`buildDeniedCard` instance (03-03 recorded stub). Fix: `wireDeniedActions`
wires BOTH instances in `web/src/main.ts`.
3. *Post-fix canaries*: `GET /` 200, unsigned `/api/session` 401, webhook armed
(`/telegram/webhook`, no last_error), menu button verified `web_app`.
**Notes for the re-run:** on plain Chrome (no Telegram), TRY AGAIN legitimately
re-renders the denied card — session can never validate without initData there;
the pass condition for leg 5 is the deliberate denied state itself. If Android
still shows dead/absent retry interplay after OS location is enabled, open a
follow-up.
## 2026-10-23 debug round 2 (no prompt / no on-device enable path)
**Symptom (legs 1 + 5):** denied card shows but no native prompt ever appears,
and the card offers no way to actually enable location — TRY AGAIN instant
re-fails when access was previously denied/services off.
**Root cause:** the D-06 card only offered chain re-run + chat fallback. When
Telegram's LocationManager has a settings-resolvable denial (access denied
earlier, or services off), the ONLY realistic recovery is Telegram's own
permission pane — `LocationManager.openSettings()` — which was typed optional
but never invoked anywhere.
**Fix (commit after round 1, deploy `bb92dbeb`):**
- Denied cards gain a THIRD CTA — `LOCATION SETTINGS / TETAPAN LOKASI`
(new strings key pair; COPY5 gates still green) — rendered on
Telegram clients only (`wa.LocationManager` presence test).
- Click → `lm.openSettings()` (gateway re-runs the chain too, covering
clients without the API).
- `locationManagerUpdated` event listener re-runs the chain the moment
the user grants, instead of waiting for another tap.
- UI-SPEC amendment: copy table + denied-CTA list append this third action
(Telegram-only conditional); rationale recorded here per debug-round
protocol.
**Re-test instruction:** restart the Telegram client (menu is cached), open
the app from the menu button, on the denied card tap LOCATION SETTINGS →
grant location in Telegram's pane → the app should reach LIVE without
another tap. Legs 1/2/6 resume from there.
## 2026-10-23 debug round 3 (definitive production root cause)
**Symptom:** cards render but the app behaves as if there is NO Telegram at all —
no initData (session always 401s), no LOCATION SETTINGS button, BackButton dead.
Even the recenter/theme machinery could not function through real Telegram.
**Root cause:** `web/index.html` never loaded `telegram-web-app.js`. Telegram does
NOT auto-inject `window.Telegram` (03-RESEARCH.md line ~687 made the opposite call —
erratum logged in RESEARCH). Every production symptom traces to `getWebApp()` → null.
**Fix (deploy `3c25dd32`):** `<script src="https://telegram.org/js/telegram-web-app.js">`
added to `<head>` before the module script. Builds + 33/33 gates green.
Erratum recorded in 03-RESEARCH.md.
**Re-test:** restart Telegram client, reopen web app from the menu button —
expect the REAL boot (BOT-01 → BOT-02 → LIVE with an actual permission ask or
a live LocationManager read + LOCATION SETTINGS available on denial).
## 2026-10-23 debug round 4 (missing LOCATION SETTINGS on the main denied card)
**Symptom:** user still sees only TRY AGAIN / ASK VIA CHAT — no LOCATION SETTINGS.
**Root cause:** executor patch-order bug — round 2 attached
`withLocationSettings` only to the awaiting-pin card instance; the primary
denied card (the one shown at denial) never got the CTA.
**Fix (deploy `253d7a3e`):** both denied-shaped instances now build with the
Telegram-only settings CTA. Deployed `index.html` verified to carry
telegram-web-app.js (round 3 fix verified live via curl).
## 2026-10-23 debug round 5 (buttons render but all appear dead)
**Symptom:** LOCATION SETTINGS now visible (round 3 confirmed — the app IS
served and parsed), but all three CTAs appear to do nothing; strip shows
LOCATION OFF.
**Working hypothesis:** the user may be viewing the app OUTSIDE a real
Telegram webview (plain Chrome address bar). With round 3's script tag,
window.Telegram.WebApp now exists even off-Telegram (SDK defines it
unconditionally), so LOCATION SETTINGS renders — but initData is empty
there → every session POST 401s → TRY AGAIN/ASK VIA CHAT both bounce back
to denied instantly (visually indistinguishable). openSettings() no-ops
off-Telegram.
**Fix (deploy `7cbb73f9`, commit `f445399`):** on-screen DIAGNOSTIC PROBE,
bottom-left, category-only: `WA <version>/<platform> · q <initData-chars> ·
s <session-category> · loc <state>`. No payload, no coordinates, no ids —
category-only law (D-14); removed at the device-matrix exit. Expected
readings:
- real Telegram webview: `WA 8.0+/android|ios` (not `none`), `q` > 0
- plain browser: `WA unknown/unknown · q 0ch · s malformed/tampered`
**Re-test:** the probe line is what to report, verbatim, in the next message.
## 2026-10-23 debug round 6 (DEFINITIVE — `s tampered` on a real webview)
**Probe reading:** `WA ... · s tampered · loc denied` — the session gate was
rejecting the REAL initData (empty-initData theories were wrong; the webview
is live and Telegram-native).
**Root cause:** `bot/src/security/initdata.ts` excluded the Ed25519
`signature` field from the HMAC data-check-string. Per the official spec
(core.telegram.org/bots/webapps §Validating data received via the Mini App),
the bot's own HMAC check excludes **only** `hash`; the signature exclusion
belongs to THIRD-PARTY validation. Modern webviews append `signature`, so
every real client failed as "tampered" while all signature-less suite
vectors passed.
**Fix (deploy `bedc8126`, 50 bot tests incl. new T2b signature-field
regression vectors):** HMAC dcs = fields minus `hash` only, in
initdata.ts + both test-side signing helpers. Erratum header updated.
**Live post-fix proof:** a minted initData WITH a signature field → 200 at
the deployed origin (pre-fix equivalent → 401/tampered).
**Re-test:** app should now validate → chain runs → permission ask /
LocationManager read → LIVE. Probe should read `s ok · loc live` on
success.