Files
arah2/.planning/phases/04-destination-search-kl-gazetteer/04-DEVICE-GATE.md
T
araharah-executor 7b02c7dd4a docs(04-04): the blocking ONBD-02/ONBD-03 device gate — deployed + canaries green, six legs UNVERIFIED pending a real device
- deploy 31138f79 live at https://araharah-bot.araharah-bot.workers.dev;
  MINI_APP_URL == origin; menu button web_app armed; commands
  re-registered (help/privacy/cari); unsigned webhook 401; the live
  app chunk carries 30/30 gazetteer ids + zero crypto tokens
- getWebhookInfo last_error is STALE (2026-10-22, phase-3 era,
  pre-dates both wave deploys; pending 0) — re-read at leg 1
- all six physical legs recorded UNVERIFIED — the phase stays open
  until a real Android/iOS/Desktop walks them; no faked verdicts
2026-10-24 01:44:51 +08:00

140 lines
8.9 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 04 — Device Gate: the ONBD-02 deep-link carrier + the ONBD-03 home-screen launch
**Deployed origin:** https://araharah-bot.araharah-bot.workers.dev/
**Deployed version:** `31138f79-3385-4079-a9a8-f1351da3b149` (`npm run deploy` — theme → web → `wrangler deploy` with `web/dist` as static assets + `set-webhook.mjs` re-arm)
**Status: PENDING REAL-DEVICE VERIFICATION — the phase does not close until every leg has an explicit verdict. Every leg below is currently `UNVERIFIED`; no device is attached to this environment. Do NOT fake device results.**
## What this gate decides
ONBD-02's carrier is the ONE requirement whose success depends on undocumented Telegram
client behaviour: the bot's OPEN button is a `web_app` inline button whose URL carries
`?dest=<plain-gazetteer-id>` (RESEARCH Pitfall 1 — `WebAppInfo` has exactly one field,
`url`, so `start_param` cannot ride an inline button). The webview reads the URL query
first, the signed `initData`'s `start_param` second, with one shared validation. Only a
real client proves Telegram actually delivers the button's query string into the webview.
## Automated half (already green — this deploy, 2026-10-23)
- `npm test -w web` — 57 passed, 0 failed (DP1–DP12 destination gates, HS1–HS9 home-screen
gates, BA1–BA8 build gates incl. the NEW BA7 bot/web id-set parity gate + BA8 no-crypto gate,
the extended GEO-03 storage gate incl. the explicit `web/src/search` pin)
- `npm test -w bot` — 65 passed, 0 failed (CR1–CR15 `/cari` payload gates; the deep-link
contract's other half)
- `npm run search:corpus` — threshold PASS: top-3 68/68 (100%), negatives refused 7/7,
refusal margin 0.643 < 0.72 (the D-14 close holds; lever: None)
- `npm run capture -w web` — **CAPTURE OK — 8/8 non-blank captures**, including the NEW
`shell-dest-preload.png` (?mock=android&dest=suria-klcc → ARAHARAH-DEST
`state=resolved` → the DESTINATION card rendered **Suria KLCC / Jalan Ampang, 50088
Kuala Lumpur** over the live map, ADD TO HOME SCREEN in the strip). GEO-03 runtime
probe: app storage surfaces empty after boot-to-live.
- Shipped-bundle canary (live origin, `cache: no-store`): `assets/main-B7xbFMG5.js`
carries **all 30/30 gazetteer ids**, ZERO crypto tokens (`createHmac`/`crypto.subtle`/
`sha256`/`HMAC(` absent), the ONBD-03 copy present. `web/dist/index.html` carries no
harness markers (BA5/BA6 hold in production).
## Deploy canaries (2026-10-23, post-deploy)
| Canary | Result |
|--------|--------|
| `getWebhookInfo.url` | `https://araharah-bot.araharah-bot.workers.dev/telegram/webhook` (ends `/telegram/webhook`) ✓ |
| `getWebhookInfo.last_error` | **STALE** — "500 Internal Server Error" dated 2026-10-22 11:18 UTC (phase-3 era, BEFORE the 04-03 and 04-04 deploys); `pending_update_count: 0`. The field cannot clear until the next real update is delivered — re-read it during leg 1 and record what you see. |
| `getChatMenuButton` | `web_app` → `https://araharah-bot.araharah-bot.workers.dev/` ✓ (was un-armed on the first canary read — re-armed with `node bot/scripts/set-menu-button.mjs`) |
| `getMyCommands` | default scope + `all_private_chats` + `ms` scope all list `help`, `privacy`, `cari` ✓ (re-registered with `node bot/scripts/register-commands.mjs`. Note: a `language_code:"en"` lookup returns `[]` because en IS the language-agnostic default set — language-specific sets exist only for `ms`. This is Telegram's scoping semantics, not a registration failure.) |
| Unsigned webhook POST | **401** (the `X-Telegram-Bot-Api-Secret-Token` gate is live) ✓ |
| `MINI_APP_URL` (wrangler vars) | `https://araharah-bot.araharah-bot.workers.dev` — equals the deployed origin exactly ✓ (binding visible in the deploy log) |
| Secrets | never echoed — the bot token was only ever concatenated into Bot API URLs, never printed ✓ |
## Bot API 10.2 origin prerequisite (hard, blocking)
- [ ] **@BotFather's Mini App URL == `https://araharah-bot.araharah-bot.workers.dev`.** The
Bot API cannot read the BotFather-side value, so this half is verified on the device: if
the origins mismatch, Bot API 10.2 silently no-ops every `Telegram.WebApp.*` call and
EVERY leg below fails with a blank-shell symptom. The bot-side origins (menu button,
OPEN button via `MINI_APP_URL`) are confirmed equal above — confirm the BotFather side
matches while walking leg 1.
Observed / Symptom: ______________________________________
## The six device legs (each: an explicit verdict + observation)
### 1. ONBD-02 — Android (the carrier that matters)
- [ ] Send `/cari KLCC` in the bot's chat → a NATIVE venue card for Suria KLCC appears with
"OPEN IN ARAHARAH" + 2–4 alternates.
- [ ] Tap OPEN → the Mini App opens **with the destination PRELOADED**: the DESTINATION card
shows **Suria KLCC / Jalan Ampang, 50088 Kuala Lumpur** over the live map.
- [ ] FAIL SYMPTOMS: a blank map, an empty card stack, or "Destination unavailable" = the
`web_app` button's query string did not survive into the webview (Carrier A collapsed →
pull the carrier-swap lever: `miniAppUrlFor(origin, id)` in `bot/src/bot/commands/cari.ts`
is the ONE place the link is built).
- [ ] While here: re-read `getWebhookInfo.last_error` — it should now be cleared (the
pre-deploy stale error dated 2026-10-22 must not reappear).
Verdict: `UNVERIFIED` (needs Android device)
Client version / OS: ____________ Observed / Symptom: ______________________________________
### 2. ONBD-02 — iOS
- [ ] The same flow on an iPhone: `/cari KLCC` → venue card → OPEN → the destination card
shows Suria KLCC preloaded.
- [ ] Expect `checkHomeScreenStatus` to report `"unknown"` (iOS platform behaviour — see
leg 4; NOT a failure).
Verdict: `UNVERIFIED` (needs iOS device)
Client version / OS: ____________ Observed / Symptom: ______________________________________
### 3. ONBD-02 — Telegram Desktop
- [ ] The same flow on Desktop: `/cari KLCC` → venue card → OPEN.
- [ ] Desktop has no Telegram geolocation (phase 3's issue #77), so observe the DESTINATION
CARD specifically — the location half is phase 3's separate matrix and is NOT this leg.
The boot lands on the denied/awaiting-pin location card first; send a chat pin (attach →
Location) to push the chain live, and the destination card then holds the center register.
- [ ] FAIL SYMPTOM: "Destination unavailable" or a blank map.
Verdict: `UNVERIFIED` (needs Desktop client)
Client version / OS: ____________ Observed / Symptom: ______________________________________
### 4. ONBD-03 — home screen add + launch
- [ ] With the app open, tap the app's "ADD TO HOME SCREEN" affordance (or Telegram's own
"Add to Home Screen" menu item) → the icon appears on the phone's home screen.
- [ ] Close the app fully → tap the icon → the app opens into the SEARCH-READY state: the
map renders, the strip shows its normal boot state, NO destination, NO crash, NO blank map
(D-12: the startapp-less boot is unchanged).
- [ ] On iOS, `checkHomeScreenStatus` reports `"unknown"` — that is the DOCUMENTED platform
limitation (Telegram-iOS #1729, closed as expected behaviour). The affordance still shows
the neutral "ADD TO HOME SCREEN" prompt. This MUST NOT be recorded as a failure.
- [ ] On Android, after adding, the affordance should HIDE (status `"added"`) — record
whether it did.
Verdict: `UNVERIFIED` (needs a phone)
Client version / OS: ____________ Observed / Symptom: ______________________________________
### 5. The honest unknown-id state
- [ ] Open the Mini App with a deliberately wrong id — append `?dest=not-a-real-place` to the
deployed URL (browser or a crafted link): expected the "DESTINATION / Destination
unavailable / That place is not in this build. Search again from the bot." surface.
- [ ] FAIL SYMPTOMS: a blank map, a fabricated pin, a crash — none may occur.
Verdict: `UNVERIFIED`
Observed / Symptom: ______________________________________
### 6. The honest refusal
- [ ] Send `/cari Kuching` in the chat → a plain-text reply naming the v1 area, with NO venue
card, NO inline keyboard, NO pin (SRCH/D-13 negative-refusal law, live on the real bot).
Verdict: `UNVERIFIED`
Observed / Symptom: ______________________________________
## If a leg fails
- Legs 1–3 (carrier): pull the carrier-swap lever — `miniAppUrlFor(origin, id)` in
`bot/src/bot/commands/cari.ts` is the single place the deep link is built (04-03 kept it
swappable for exactly this contingency). The webview reader already accepts BOTH carriers.
- Leg 4: the affordance is behind the Bot API 8.0 guard — record the client's Bot API
version (`wa.version`) if it no-ops.
- Record the verdict inline above, then update `.planning/STATE.md` "Blockers/Concerns"
(the executor leaves this to the orchestrator) — the phase stays OPEN on any
FAIL/UNVERIFIED, never silently closed on unverified hardware.
---
*Phase: 04-destination-search-kl-gazetteer — plan 04-04 blocking gate*
*Automated legs + deploy: 2026-10-23; physical legs: pending user*