Compare commits
10
Commits
f4453996ac
...
1890b5b1ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1890b5b1ca | ||
|
|
8a5c8b415f | ||
|
|
2589e2126f | ||
|
|
9d6fdc64be | ||
|
|
fc99745672 | ||
|
|
28045d9b9e | ||
|
|
19a51e5882 | ||
|
|
e64cf7003b | ||
|
|
22fda6d12a | ||
|
|
c062ebaee1 |
@@ -132,3 +132,11 @@ Success Criterion 5 remains open until the user reports results.
|
||||
**Resume signal:** "device matrix passed", a per-leg failure report, or a
|
||||
no-device-available report (→ Success Criterion 5 = UNVERIFIED; recommend Phase-7
|
||||
deferral with GEO-01/GEO-02 noted unverified on hardware).
|
||||
|
||||
## Checkpoint resolution (orchestrator, 2026-10-23)
|
||||
|
||||
Task 3 (device-matrix) resolved **PASSED** by the user after debug rounds 1–6
|
||||
(see 03-DEVICE-MATRIX.md). Notable in-gate fixes: telegram-web-app.js
|
||||
integration (round 3), HMAC signature-field erratum (round 6), web_app menu
|
||||
button + settings CTA (rounds 1–4). iOS leg recorded UNVERIFIED — no device.
|
||||
Verification: 03-VERIFICATION.md — phase 3 PASSED.
|
||||
|
||||
@@ -183,3 +183,88 @@ 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.
|
||||
|
||||
## 2026-10-23 round 7 — probe readings (legs largely passing)
|
||||
|
||||
- **Android Telegram:** `s ok`, app live — leg 1 core WORKS.
|
||||
- **Telegram Desktop:** `s ok · loc live` — tdesktop webview delivered a real
|
||||
geolocation fix natively (better than the assumed issue #77 denial class);
|
||||
GEO-02's denied path remains provable on the plain-browser leg / clients
|
||||
where geolocation is unavailable.
|
||||
- **Plain Chrome:** `WA 6.0/unknown · q 0ch · s malformed · loc denied` — the
|
||||
DESIGNED off-Telegram state; leg 5's pass condition (denial is deliberate,
|
||||
not blank). No webview can work without Telegram identity — correct.
|
||||
|
||||
**Cosmetic fix (`19a51e5`, deploy `1a6947ef`):** LOCATION SETTINGS was visible
|
||||
off-Telegram (SDK stub defines LocationManager and reports 6.0) where
|
||||
`openSettings()` is a no-op. CTA eligibility now shares the chain's
|
||||
`isVersionAtLeast("8.0")` gate → the button hides on unsupported clients.
|
||||
|
||||
**Remaining for the exit:** Android boot-state choreography + reopen
|
||||
(no second prompt), Android stale-fix walk (A1), iOS (device permitting),
|
||||
mid-session theme flip leg, then verdicts on all six legs + probe removal.
|
||||
|
||||
---
|
||||
|
||||
## FINAL VERDICT — 2026-10-23: **PASSED** (user: "device matrix passed")
|
||||
|
||||
| Leg | Verdict |
|
||||
|---|---|
|
||||
| 0. BotFather origin prerequisite | ✅ workers.dev origin |
|
||||
| 1. Android boot + persistence | ✅ LIVE, no second prompt |
|
||||
| 2. Android stale-fix (A1) | ✅ per user pass; A1 notes stand in 03-RESEARCH.md |
|
||||
| 3. iOS | ⚠ UNVERIFIED — no device (honest; Phase 7 hardening item) |
|
||||
| 4. Desktop Telegram | ✅ native fix (`s ok · loc live`) — issue #77 denial class not hit on this client; chat-KB route remains test-gated |
|
||||
| 5. Plain browser | ✅ deliberate denied state — evidence: `leg5-plain-browser-evidence.png` |
|
||||
| 6. Theme flip mid-session | ✅ per user verdict |
|
||||
|
||||
Probe removed at exit (commit `fc99745`, deploy `dc0307ea`). Verification: 03-VERIFICATION.md.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Phase 3: Mini App Shell + Geolocation — Verification
|
||||
|
||||
**Verified:** 2026-10-23
|
||||
**Method:** goal-backward against ROADMAP §Phase 3 success criteria, after in-execution debug rounds 1–6 (see 03-DEVICE-MATRIX.md) and the user's final device-matrix verdict ("device matrix passed").
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### SC1 (GEO-01) — Android: grant, live position on Rams basemap, permission persists — ✅ PASS
|
||||
- Real Android phone, Telegram webview: boot reaches LIVE, `s ok` (probe round 7), position marker on the Rams basemap.
|
||||
- Probe round 6 root-caused the last blocker: the server's HMAC validator excluded the Ed25519 `signature` field from the data-check-string (spec: exclude ONLY `hash` for the bot's own check). Fixed + regression-tested (T2b vectors, 50 bot tests) + live-verified (signed-with-signature initData → 200).
|
||||
- User-confirmed reopen behavior: no second permission prompt.
|
||||
|
||||
### SC2 (GEO-02) — fallback chain recovers origin — ✅ PASS (with a recorded upgrade)
|
||||
- Telegram Desktop delivered a **native** geolocation fix (`s ok · loc live`) — stronger than the designed denial-class fallback (research's issue-#77 assumption retired on that client).
|
||||
- The chat-keyboard fallback route is fully implemented and gated: `POST /api/location-fallback` (claim-scoped), `GET /api/location` relay, 60s bounded client poll, `?pin=1` harness capture — 48 bot + 33 web tests green. Not exercised live on-device because no tested client landed in the denied class during the matrix; the plain-browser leg proves the deliberate denied surface.
|
||||
|
||||
### SC3 (PX-02) — deliberate denied state, themes, Rams-constant map — ✅ PASS
|
||||
- Plain-browser leg evidence: `leg5-plain-browser-evidence.png` (real-browser capture at the deployed origin) — denied card over the Rams shell, red LOCATION OFF lamp, no blank, no off-Telegram settings CTA (version gate `19a51e5`).
|
||||
- 7/7 capture suite includes light + dark live shots; theme-slot machine tested (PX02 gates); copy en+ms verbatim from the UI-SPEC (COPY1–6 gates).
|
||||
|
||||
### SC4 (GEO-03) — no coordinate persists — ✅ PASS
|
||||
- Automated runtime probe: localStorage/sessionStorage/cookies/IndexedDB all empty after a real boot-to-live cycle (03-04 capture harness).
|
||||
- Session token memory-only (D-03/D-07); KV namespaces TTL-only (PG1/PG2 gates from 03-01).
|
||||
|
||||
### SC5 — device-matrix exit pass — ✅ PASS (user verdict, 2026-10-23)
|
||||
- Android ✅ · Telegram Desktop ✅ (native fix) · plain browser ✅ (denied by design) · theme flip ✅ (per user verdict) · iOS — not available: **UNVERIFIED — no device** (recorded honestly per the matrix protocol; does not block v1 KL-metro scope, flagged for Phase 7 hardening).
|
||||
|
||||
## Debug rounds during execution (summary)
|
||||
|
||||
| Round | Symptom | Root cause | Fix |
|
||||
|---|---|---|---|
|
||||
| 1 | No webview entry; dead CTAs on awaiting card | Menu button was `commands` (phase 2); wiring only on first denied instance | `set-menu-button.mjs` (web_app); wire both instances |
|
||||
| 2 | No prompt / no enable path | `openSettings()` never invoked | LOCATION SETTINGS CTA + `locationManagerUpdated` re-run |
|
||||
| 3 | App behaves as off-Telegram everywhere | `telegram-web-app.js` never loaded (research erratum) | Script tag in `<head>` |
|
||||
| 4 | Settings CTA missing on main denied card | Patch attached to wrong instance | Both instances |
|
||||
| 5 | Buttons "dead" | Diagnostic probe added (removed at exit) | On-screen category-only probe |
|
||||
| 6 | `s tampered` on real clients | HMAC dcs excluded `signature` (spec: hash only) | Validator fix + T2b regressions + live 200 proof |
|
||||
| 7 | Probe readings decoded; CTA visible off-Telegram | SDK stub defines LocationManager at v6.0 | CTA eligibility = chain's 8.0 version gate |
|
||||
|
||||
## Requirements coverage
|
||||
|
||||
- **GEO-01** ✅ · **GEO-02** ✅ (native-desktop upgrade recorded) · **GEO-03** ✅ · **PX-02** ✅
|
||||
|
||||
## Verdict
|
||||
|
||||
**PHASE 3: PASSED** — 4/4 plans executed; 5/5 success criteria met (iOS leg honestly UNVERIFIED, no device). Phase 4 (destination-search-+-kl-gazetteer) is next.
|
||||
|
||||
## Exit addendum (2026-10-23, post-verdict)
|
||||
|
||||
- Diagnostic probe removed (commit `fc99745`, deploy `dc0307ea`) — the probe's
|
||||
exit obligation; final capture suite 7/7 green without it.
|
||||
- GEO-03 runtime probe updated: the official `telegram-web-app.js` (debug
|
||||
round 3) writes exactly one sessionStorage key (`__telegram__initParams`,
|
||||
SDK launch bookkeeping, no coordinates). The probe now allowlists that
|
||||
single platform key and still fails on ANY other write (`8a5c8b4`).
|
||||
Our app's own surfaces remain byte-empty — the D-07 law holds.
|
||||
- Final gates at close: web 33/33 · bot 50/50 · captures 7/7 · theme audit clean.
|
||||
@@ -10,8 +10,14 @@
|
||||
* RED suite's node:crypto signing path, RFC-4231-pinned):
|
||||
* 1. parse `raw` as a Telegram-style querystring (values URL-decoded ONCE;
|
||||
* `+` stays literal — do NOT use URLSearchParams)
|
||||
* 2. dataCheckString = fields minus `hash`/`signature`, sorted by FIELD
|
||||
* 2. dataCheckString = fields minus `hash` ONLY, sorted by FIELD
|
||||
* NAME (byte order, JS default sort), `key=value`, joined with "\n"
|
||||
* (ERRATUM 2026-10-23, device-matrix round 6: the HMAC path must NOT
|
||||
* exclude `signature` — per core.telegram.org/bots/webapps the bot's
|
||||
* own check excludes only `hash`; the signature exclusion belongs to
|
||||
* THIRD-PARTY Ed25519 validation. Real webviews append `signature`,
|
||||
* and excluding it produced "tampered" rejections on live clients
|
||||
* while every synthetic suite vector — signature-less — passed.)
|
||||
* 3. secretKey bytes = HMAC-SHA256(message = utf8(botToken),
|
||||
* key = utf8(the salt constant, line 179))
|
||||
* checkHex = hex(HMAC-SHA256(message = utf8(dataCheckString),
|
||||
@@ -168,9 +174,11 @@ export async function validateInitData(
|
||||
return { ok: false, reason: "malformed" as const };
|
||||
}
|
||||
|
||||
// — data-check-string: hash/signature excluded, sorted by field name —
|
||||
// — data-check-string: hash excluded ONLY (signature participates — the
|
||||
// HMAC spec excludes just `hash`; excluding it broke real clients,
|
||||
// device-matrix round 6) — sorted by field name —
|
||||
const dataCheckString = [...fields.entries()]
|
||||
.filter(([key]) => key !== "hash" && key !== "signature")
|
||||
.filter(([key]) => key !== "hash")
|
||||
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("\n");
|
||||
|
||||
@@ -52,10 +52,12 @@ function loadValidator() {
|
||||
|
||||
/* ————— node:crypto signing helpers (test-side, independent path) ————— */
|
||||
|
||||
/** Data-check-string: fields sorted by key, key=value, LF-joined (Telegram spec). */
|
||||
/** Data-check-string: fields sorted by key, key=value, LF-joined (Telegram
|
||||
* spec — ALL fields except `hash`; the signature field participates in the
|
||||
* bot's own HMAC check, device-matrix round 6 erratum). */
|
||||
function dataCheckString(fields) {
|
||||
return fields
|
||||
.filter(([key]) => key !== "hash" && key !== "signature")
|
||||
.filter(([key]) => key !== "hash")
|
||||
.slice()
|
||||
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
@@ -104,6 +106,43 @@ test("T1 known-good signed initData → ok:true, user.id 42, languageCode ms", a
|
||||
|
||||
/* ————— T2 — flipped hash character → tampered ————— */
|
||||
|
||||
/* T2b — ERRATUM regression (device-matrix round 6): real webviews append the
|
||||
* Ed25519 `signature` field; per the official HMAC spec the data-check-string
|
||||
* excludes ONLY `hash`, so a signed body carrying `signature` must validate.
|
||||
* The old implementation excluded `signature` from the HMAC string and
|
||||
* rejected every live client as "tampered" while all signature-less suite
|
||||
* vectors passed — the classic blind spot this test pins. */
|
||||
|
||||
test("T2b real-shaped initData WITH a signature field → ok:true", async () => {
|
||||
const validateInitData = await loadValidator();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const raw = buildInitData([
|
||||
["query_id", "AAH3xSample"],
|
||||
["signature", "c2FtcGxlX3NpZ25hdHVyZV9icnVmX2Jhc2U2NA"],
|
||||
["user", JSON.stringify({ id: 42, first_name: "Zul", language_code: "ms" })],
|
||||
["auth_date", String(now)],
|
||||
]);
|
||||
const res = await validateInitData(raw, TEST_TOKEN);
|
||||
assert.equal(res.ok, true, `expected ok:true with signature field, got ${JSON.stringify(res)}`);
|
||||
});
|
||||
|
||||
test("T2b flipped hash with signature present → still tampered", async () => {
|
||||
const validateInitData = await loadValidator();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const raw = buildInitData([
|
||||
["query_id", "AAH3xSample"],
|
||||
["signature", "c2FtcGxlX3NpZ25hdHVyZV9icnVmX2Jhc2U2NA"],
|
||||
["user", JSON.stringify({ id: 42, first_name: "Zul", language_code: "ms" })],
|
||||
["auth_date", String(now)],
|
||||
]);
|
||||
const match = /hash=[0-9a-f]{8}/.exec(raw);
|
||||
assert.ok(match !== null);
|
||||
const tampered = raw.slice(0, match.index) + `hash=${(createHmac("sha256", "seed").update("x").digest("hex"))}${raw.slice(match.index + "hash=".length + 64)}`;
|
||||
const res = await validateInitData(tampered, TEST_TOKEN);
|
||||
assert.equal(res.ok, false);
|
||||
assert.equal(res.reason, "tampered");
|
||||
});
|
||||
|
||||
test("T2 flipped hash character → reason tampered", async () => {
|
||||
const validateInitData = await loadValidator();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
@@ -38,7 +38,7 @@ const TEST_TOKEN = "12345:TESTTOKEN"; // same signer token as initdata.test.mjs
|
||||
|
||||
function dataCheckString(fields) {
|
||||
return fields
|
||||
.filter(([key]) => key !== "hash" && key !== "signature")
|
||||
.filter(([key]) => key !== "hash")
|
||||
.slice()
|
||||
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
|
||||
+11
-4
@@ -290,17 +290,24 @@ try {
|
||||
]);
|
||||
if (signal === 'timeout') throw new Error('GEO-03 probe leg: ARAHARAH-LIVE never arrived within 30000ms');
|
||||
await page.waitForTimeout(1500);
|
||||
/* Platform allowlist (device-matrix exit addendum): the OFFICIAL
|
||||
* telegram-web-app.js (added in debug round 3) writes exactly one
|
||||
* sessionStorage key — `__telegram__initParams`, the SDK's own launch
|
||||
* bookkeeping ({}, or Telegram-supplied launch params). It is not app
|
||||
* data, carries no coordinates, and the D-07 law binds OUR surfaces —
|
||||
* which remain byte-empty. Any OTHER key still fails the probe. */
|
||||
const probe = await page.evaluate(async () => ({
|
||||
local: localStorage.length,
|
||||
session: sessionStorage.length,
|
||||
foreignSessionKeys: Object.keys(sessionStorage).filter((k) => k !== '__telegram__initParams'),
|
||||
ssForeignOnly: Object.keys(sessionStorage).every((k) => k === '__telegram__initParams'),
|
||||
cookie: document.cookie,
|
||||
dbs: (await indexedDB.databases()).length,
|
||||
lsKeys: Object.keys(localStorage),
|
||||
}));
|
||||
if (probe.local !== 0 || probe.session !== 0 || probe.cookie !== "" || probe.dbs !== 0) {
|
||||
failures.push(`GEO-03 runtime probe FAILED — localStorage=${probe.local} sessionStorage=${probe.session} cookie="${probe.cookie}" indexedDB=${probe.dbs} keys=${JSON.stringify(probe.lsKeys)}`);
|
||||
if (probe.local !== 0 || !probe.ssForeignOnly || probe.cookie !== "" || probe.dbs !== 0) {
|
||||
failures.push(`GEO-03 runtime probe FAILED — localStorage=${probe.local} foreignSessionKeys=${JSON.stringify(probe.foreignSessionKeys)} cookie="${probe.cookie}" indexedDB=${probe.dbs} keys=${JSON.stringify(probe.lsKeys)}`);
|
||||
} else {
|
||||
log.push('GEO-03 runtime probe PASSED — localStorage/sessionStorage/cookies/IndexedDB all empty after boot-to-live');
|
||||
log.push('GEO-03 runtime probe PASSED — app storage surfaces empty after boot-to-live (platform allowlist: SDK-owned __telegram__initParams only)');
|
||||
}
|
||||
} catch (err) {
|
||||
failures.push(`GEO-03 runtime probe: ${err.message}`);
|
||||
|
||||
+3
-10
@@ -28,7 +28,7 @@ import { createPositionMarker } from "./map/marker.ts";
|
||||
import { createFollowController } from "./map/follow.ts";
|
||||
import { createStatusStrip } from "./ui/strip.ts";
|
||||
import { buildBootCard, buildDeniedCard, buildSignalLostCard, startGantry, stopGantry } from "./ui/cards.ts";
|
||||
import { createProbe } from "./ui/probe.ts";
|
||||
import { telegramLocationSupported } from "./location/telegram.ts";
|
||||
import { createRecenterControl } from "./ui/recenter.ts";
|
||||
import { getWebApp } from "./telegram/webapp.ts";
|
||||
import { watchTheme } from "./telegram/theme.ts";
|
||||
@@ -98,18 +98,13 @@ async function boot(): Promise<void> {
|
||||
if (lastFix !== null && map !== null) follow?.follow(lastFix);
|
||||
});
|
||||
|
||||
// DEBUG-ROUND probe: category-only on-screen telemetry for the device
|
||||
// matrix (webview has no dev-tools); removed at the exit.
|
||||
const probe = createProbe(mainEl);
|
||||
probe.set({ wa: wa ? `${wa.version ?? "?"}/${wa.platform ?? "?"}` : "none", initDataChars: wa ? wa.initData.length : null });
|
||||
|
||||
// Cards are built once at boot (boot-time locale) and reused across re-runs.
|
||||
const bootCard1 = buildBootCard(locale, 1);
|
||||
const bootCard2 = buildBootCard(locale, 2);
|
||||
const deniedCard = buildDeniedCard(
|
||||
locale,
|
||||
// Debug round 2/3: Telegram-only deep-link via LocationManager.openSettings.
|
||||
{ awaitingPin: false, withLocationSettings: wa?.LocationManager !== undefined },
|
||||
{ awaitingPin: false, withLocationSettings: telegramLocationSupported(wa) },
|
||||
);
|
||||
const awaitingCard = buildDeniedCard(
|
||||
locale,
|
||||
@@ -117,7 +112,7 @@ async function boot(): Promise<void> {
|
||||
// is the whole eligibility test — off-Telegram (Desktop/browser) there
|
||||
// is nothing for openSettings() to open, and navigator.geolocation
|
||||
// denial is handled by the OS settings hint copy instead.
|
||||
{ awaitingPin: true, withLocationSettings: wa?.LocationManager !== undefined },
|
||||
{ awaitingPin: true, withLocationSettings: telegramLocationSupported(wa) },
|
||||
);
|
||||
const lostCard = buildSignalLostCard(locale);
|
||||
const gantryOf = (card: HTMLElement): HTMLElement => {
|
||||
@@ -188,7 +183,6 @@ async function boot(): Promise<void> {
|
||||
}
|
||||
|
||||
function renderState(state: LocationState): void {
|
||||
probe.set({ location: state.kind });
|
||||
const ageSec = lastFix !== null ? fixAgeSeconds(lastFix, Date.now()) : null;
|
||||
const accuracyM = lastFix !== null ? lastFix.accuracyM : null;
|
||||
strip.render(state, ageSec, accuracyM);
|
||||
@@ -240,7 +234,6 @@ async function boot(): Promise<void> {
|
||||
async function runFromSession(): Promise<void> {
|
||||
renderState({ kind: "validating" });
|
||||
const session = await createSession(wa?.initData ?? "");
|
||||
probe.set({ session: session.ok ? "ok" : session.reason, initDataChars: wa ? wa.initData.length : null });
|
||||
if (!session.ok) {
|
||||
staleHandled = false; // session-first failure is boot-level, not a stale-fix recovery
|
||||
renderDenied();
|
||||
|
||||
+1
-5
@@ -91,8 +91,4 @@ main { position: fixed; inset: 0; }
|
||||
.pos-group[data-stale="true"] { opacity: 0.4; }
|
||||
|
||||
/* No CSS transition or animation anywhere — state changes are instant hard swaps. */
|
||||
|
||||
/* DEBUG-ROUND probe — on-screen category-only telemetry for the device-matrix
|
||||
loop (no dev-tools in a phone webview). Removed at the exit. Layout-only
|
||||
positioning; the label typography is the existing label-sm token. */
|
||||
.diag-probe { position: absolute; left: var(--spacing-margin); bottom: calc(var(--safe-bottom, 0px) + var(--spacing-space-sm)); font: var(--type-label-sm); letter-spacing: var(--type-label-sm-tracking); color: var(--color-soft-beige); z-index: 10; pointer-events: none; }
|
||||
/* DEBUG-ROUND probe styles removed at the device-matrix exit (2026-10-23). */
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* probe.ts — DEBUG-ROUND diagnostic line (phase 3 device-matrix troubleshooting).
|
||||
* ----------------------------------------------------------------------------
|
||||
* One category-only instrument line, fixed bottom-left: which WebApp the app
|
||||
* sees (version/platform), the character-count of the raw initData (a COUNT,
|
||||
* never its content), the last session-gate category, and the location-chain
|
||||
* category. No coordinates, no payload, no ids — same law as the Worker's
|
||||
* category-only logs (D-14), only surfaced on-screen because a phone
|
||||
* webview has no dev-tools access during the device-matrix pass.
|
||||
*
|
||||
* Rendering uses only machine tokens (letters, digits, separators) — no
|
||||
* human copy, so no strings-module entry and no COPY5 concern. The probe is
|
||||
* expected to be REMOVED or reduced at the device-matrix exit.
|
||||
*/
|
||||
export interface ProbeParts {
|
||||
wa: string;
|
||||
initDataChars: number | null;
|
||||
session: string;
|
||||
location: string;
|
||||
}
|
||||
|
||||
export interface Probe {
|
||||
el: HTMLElement;
|
||||
set(parts: Partial<ProbeParts>): void;
|
||||
}
|
||||
|
||||
export function createProbe(parent: HTMLElement): Probe {
|
||||
const el = document.createElement("div");
|
||||
el.className = "diag-probe";
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
const parts: ProbeParts = { wa: "-", initDataChars: null, session: "-", location: "-" };
|
||||
const state = { parts };
|
||||
function render(): void {
|
||||
const p = state.parts;
|
||||
const q = p.initDataChars === null ? "-" : `${p.initDataChars}ch`;
|
||||
el.textContent = `WA ${p.wa} · q ${q} · s ${p.session} · loc ${p.location}`;
|
||||
}
|
||||
parent.appendChild(el);
|
||||
render();
|
||||
return {
|
||||
el,
|
||||
set(delta: Partial<ProbeParts>): void {
|
||||
Object.assign(state.parts, delta);
|
||||
render();
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user