Files

13 KiB
Raw Permalink Blame History

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: PASS (2026-10-24, user walk: destination card Suria KLCC renders and holds — phase-4 terminal surface; routing is Phase 5) 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: PASS (2026-10-24, user walk: destination card Suria KLCC renders and holds — phase-4 terminal surface; routing is Phase 5) 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: PASS (2026-10-24, user walk: destination card observed; then the normal location chain per client — acquiring/denied as specced) 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: PASS (2026-10-24, user walk: home-screen relaunch lands the app booted in Telegram — search-ready) Client version / OS: ____________ Observed / Symptom: ______________________________________

5. The honest unknown-id state

  • Carrier note (orchestrator, 2026-10-24): a plain browser CANNOT observe this leg — with no initData the boot stops at the session wall (denied card) before the destination reader runs. Verified live 2026-10-24: ?dest=not-a-real-place in desktop Chrome → the denied card (leg5-browser-sessionwall.png). That is correct off-Telegram behavior, NOT this leg's verdict.
  • On-device procedure: in ANY chat, send yourself the deep link https://t.me/arah2bot?startapp=not-a-real-place and tap it — Telegram opens the Mini App with start_param=not-a-real-place (the secondary carrier), the session validates, and the reader must show the honest surface.
  • Expected: "DESTINATION / Destination unavailable / That place is not in this build. Search again from the bot." — never a blank map, never a fabricated pin, never a crash. Verdict: PASS (2026-10-24, user walk) Observed: destination card "Suria KLCC" renders and holds — the phase-4 terminal surface (routing begins Phase 5). Android + iOS + Desktop all showed the card; Desktop then continued into the normal location chain (acquiring / denied per client) exactly as specced.

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: PASS (2026-10-24, user walk) Observed: destination card "Suria KLCC" renders and holds — the phase-4 terminal surface (routing begins Phase 5). Android + iOS + Desktop all showed the card; Desktop then continued into the normal location chain (acquiring / denied per client) exactly as specced.

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

Post-gate findings (2026-10-24, continued)

Legs 1–4 + 6 PASS; leg 5 FAIL (BotFather Main Mini App — user completing).

/cari missing from the command menu — diagnosis chain

  1. Commands ARE registered in default + ms scope via API (getMyCommands proves it).
  2. A STALE all_private_chats set (phase-2 era: help/privacy only) exists server-side and SHADOWS default (Telegram resolves a chat's menu from the narrowest scope with any commands).
  3. Attempted fixes: re-armed all_private_chats (en+ms), cleared junk group scopes, armed the user's chat scope directly — every non-default setMyCommands write returns ok:true but stores nothing (control: default-scope canary write lands fine; canary reads on all_private_chats/chat scopes unchanged across 100s+).
  4. register-commands.mjs rewritten to arm/clear ALL scopes durably (commit 54a2b54)
    • gates updated (CS5/CR13: single fetch site, shadow law pinned).

Status: Telegram-side silent-write defect on non-default scopes. Two outcomes:

  • If the user's client NOW shows /cari after refresh → the writes landed server-side and only getMyCommands reads are cached — done.
  • If still help/privacy → the stale set is unreachable by API; retry the register script later (node bot/scripts/register-commands.mjs) — non-default writes worked for this same token in phase 2, so this is plausibly a transient DC defect, and default scope + client cache may also simply recover it.

FINAL VERDICT — 2026-10-24: PASSED (user: "everything else is approved")

Leg Verdict
1. Android deep-link hand-off PASS — destination card preloaded
2. iOS deep-link hand-off PASS
3. Desktop deep-link hand-off PASS — destination card + normal location chain
4. Home-screen relaunch PASS
5. Unknown-id state ACCEPTED (user) — BotFather Main Mini App now configured (carrier fixed); the honest-unavailable surface itself is covered by automated DP gates + the ?mock capture; on-device re-send of the startapp link not re-confirmed after the BotFather change (user deferred)
6. Out-of-area refusal PASS

Post-gate fixes shipped during this round: map tilt + building extrusion (c6bbd414), /start logo (135621f), command-scope shadow law (54a2b54). Live deploy 41dedd84. Known-open cosmetic: /cari absent from the client command menu (Telegram non-default setMyCommands defect — diagnosis above; command itself works when typed).