--- phase: 07-hardening-private-launch plan: 06 subsystem: qa-gates tags: [launch-gate, device-matrix, battery-record, privacy-audit, budget-audit, canary, setmycommands, deferred-debt] # Dependency graph requires: - phase: 07-03 provides: scripts/privacy-audit.mjs + scripts/lib/source-gates.mjs — the runtime-hosts gate the /help pointer adjudication extends, and the --live probe contract - phase: 07-04 provides: scripts/budget-audit.mjs + docs/BUDGETS.md — the KV-list over-ceiling finding and the signature block this gate signs - phase: 07-05 provides: docs/KNOWN-LIMITATIONS.md + the /help pointer line (en + ms) — the shipped doc the checklist verifies - phase: 06-DEVICE-GATE.md provides: the never-fake device-gate protocol and the file structure this gate copies provides: - ".planning/phases/07-hardening-private-launch/07-LAUNCH-GATE.md — the UNSIGNED launch protocol: Part A (4 clients × 7 interactions, PASS/FAIL/NOT RUN), Part B (11-field Android battery record), Part C (the 12 automated-half commands), Part D (the six D-09 checklist items), executor notes, unsigned FINAL VERDICT" - "Audit-green tree restored: npm run privacy-audit exits 0 again (the 07-05 /help pointer adjudicated as a documented doc-pointer static-text class, SA6-pinned)" - "The /cari menu defect ROOT-CAUSED and FIXED: register-commands.mjs sent the scope under the wrong Bot API field name (bot_command_scope; the field is scope) — Telegram silently ignored it, every call hit the default scope, the clear calls wiped the default set; all private-chat scopes now read back help, privacy, cari" - "Fresh deploy dd55a073 with canaries verified: webhook armed (secret-token channel, no fresh errors), menu button web_app, all six command scopes armed + read-back proven, _headers policy observed live (immutable assets / revalidating HTML)" affects: [the human launch gate (Parts A–D are walked and signed), STATE.md milestone completion record (written by the orchestrator AFTER signing), forks (the gate file as onboarding evidence)] # Tech tracking tech-stack: added: [] patterns: - "Scope adjudications stay NARROW and pinned: a static-text host class consults ONLY bare URL literals — a fetch( target naming the same host still fails (SA6)" - "A quoted string is always a fetch TARGET — a URL colon must not masquerade as a TS annotation colon" - "canary read-back with the CORRECT API parameter names: setMyCommands ok:true proved nothing about scope until getMyCommands read back per scope" - "canary staleness protocol: getWebhookInfo.last_error cannot clear until the next real update — record last_error_date, compare against the deploy timeline" key-files: created: - .planning/phases/07-hardening-private-launch/07-LAUNCH-GATE.md modified: - scripts/lib/source-gates.mjs - scripts/test/privacy-audit.test.mjs - docs/PRIVACY-AUDIT.md - docs/KNOWN-LIMITATIONS.md - bot/scripts/register-commands.mjs - bot/README.md - bot/src/bot/commands/start.ts - web/src/shell.css - README.md, AGENTS.md, 4 mockup screens (logo residue) key-decisions: - "The /help pointer host (gitmirah) is adjudicated as a SOURCE-side doc-pointer static-text class following the audit's own laws — the URL stays the repo's actual published remote (07-05's decision); the class is narrow (URL literals only) and SA6-pinned; forks replace it with their own origin" - "The KV-list over-ceiling (1,440/day vs 1,000/day) is NOT auto-fixed: it is a designed product-timing decision (the due-card math couples tick and cadence) carried to the human gate with the documented options — a 90 s tick is NOT expressible in cron (minimum granularity 60 s)" - "The kv-expiry --live leg stays NOT RUN: it needs CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID which the executor's environment does not carry — an auth gate surfaced to the maintainer, never faked; a manual wrangler corroboration (namespace lists []) IS recorded" requirements-completed: [] # PX-04 completes at the SIGNED gate (task 2); the orchestrator marks requirements # Metrics duration: 47min completed: 2026-10-25 --- # Phase 7 Plan 06: Launch Gate (automated prep) Summary **The unsigned launch-gate protocol written (4×7 device matrix, 11-field battery record, 12-command automated half, the six D-09 checklist items), the audit-green state restored (the 07-05 `/help` pointer adjudicated lawfully), the `/cari` menu defect root-caused to a wrong Bot API field name and FIXED, and the fresh deploy verified by the full canary protocol.** ## Performance - **Duration:** 47 min (21:15Z → 22:02Z) - **Started:** 2026-10-24T21:15:21Z - **Automated prep completed:** 2026-10-24T22:02Z - **Tasks:** 1/3 executed (task 2 = THE BLOCKING HUMAN GATE; task 3 = orchestrator-owned, deferred per the spawn objective) - **Files modified:** 15 (1 created, 14 modified/landed) ## Accomplishments - **07-LAUNCH-GATE.md** exists as a complete, unsigned protocol: Part A — the ONE consolidated walk (D-05): Android + iOS + Telegram Desktop + plain browser × (boot · search→route · guidance · minimize/resume · trip-end · theme flip · landscape/fullscreen), ⬜ cells, the PASS/FAIL/NOT RUN vocabulary defined in the file's own voice, and per-interaction observable pass conditions; Part B — the 11-field Android battery record with the no-extrapolation rule; Part C — the 12 exact automated-half commands with ⬜ result cells and the "a NOT-RUN --live probe is NOT a pass" note; Part D — the six D-09 checklist items, each naming its evidence; executor notes carrying every known-open item; an unsigned dated FINAL VERDICT block - **Audit-green restored:** the deferred-items #1 RED (07-05's `http://gitmirah:3000` /help pointer, 2 runtime-hosts violations) fixed per the gates' OWN laws — a documented `DOC_POINTER_STATIC_TEXT_HOSTS` class on the source-side runtime-hosts gate, NARROW (URL literals only) and pinned by the new SA6 vector; `npm run privacy-audit` → **PRIVACY AUDIT CLEAN — 5 gate(s) passed, 0 violations**, suite 13/13 - **Rule 1 bonus (found by SA6's RED):** the fetch( call-site handler's declaration heuristic misclassified any quoted absolute URL as a TS method declaration — quoted fetch targets now reach the host check - **The /cari menu defect's true root cause found and FIXED at the canary:** the Bot API parameter is `scope` (verified against core.telegram.org/bots/api); the script's `bot_command_scope` was silently ignored so every call hit the default scope and the two "clear" calls wiped the default set entirely. Registration re-run; `getMyCommands` read back per scope with the correct param: default (agnostic/en/ms) + all_private_chats (agnostic/ms) all list `help, privacy, cari`; group scopes verified cleared. The limitations doc's wrong attribution corrected; residual client menu-caching caveat + workaround kept - **Fresh deploy `dd55a073` + full canary protocol verified:** webhook armed with the secret-token channel (`last_error` stale from the phase-3 era, `last_error_date` 2026-10-22T11:18Z recorded, pending 0), menu button `web_app` → deployed origin, all command scopes armed, secrets in gitignored `.dev.vars` only; the live asset-headers leg **PASS** (immutable `/assets/*`, revalidating HTML — 07-04's `_headers` policy observed on the shipped origin) - **Part C green at prep time:** bot 126/126, web 165/165, routing 65/65, search 57/57, both corpora PASS, ladder p95 74.055 ms (documented c3 band), privacy-audit CLEAN, budget-audit CLEAN, captures 22/22 ## task Commits Each unit committed atomically: 1. **fold 07-03 residue (status-first header probe, XHTML namespace, evidence-reason contract)** — `7a1206d` (fix) 2. **RED — SA6 doc-pointer vector** — `b06c64d` (test) 3. **GREEN — the doc-pointer static-text class + PRIVACY-AUDIT.md adjudication + dated addendum** — `2489cca` (fix) 4. **logo-asset replacement residue (quick task 261024-001, commit recorded TBD in STATE)** — `b7576c1` (chore) 5. **landscape cockpit geometry refinement (07-02 device-walk feedback) + re-run captures** — `71fff10` (fix) 6. **/cari root-cause fix + limitations-doc correction + README row** — `141d6d2` (fix) 7. **the launch gate protocol file** — `53930eb` (docs) ## Files Created/Modified - `.planning/phases/07-hardening-private-launch/07-LAUNCH-GATE.md` — NEW: the unsigned protocol - `scripts/lib/source-gates.mjs` — DOC_POINTER_STATIC_TEXT_HOSTS + the fetch-handler fix - `scripts/test/privacy-audit.test.mjs` — the SA6 vector (plus the folded CP2/AU1 contract updates) - `docs/PRIVACY-AUDIT.md` — the adjudication + dated gate-prep re-run addendum - `docs/KNOWN-LIMITATIONS.md` — the /cari section corrected to the real root cause - `bot/scripts/register-commands.mjs` — `scope` field name + the fix documented at the site - `bot/README.md`, `README.md`, `AGENTS.md`, 4 mockup screens, `bot/src/bot/commands/start.ts`, `web/src/shell.css`, 6 captures — residue landed ## Decisions Made - **Adjudicate, don't weaken:** the pointer URL keeps its host (07-05's documented decision — the repo's actual remote); the AUDIT gains a documented, narrow static-text class. A protocol-relative or host-neutral rewrite would have gamed the scanner while leaving drivers a dead link. - **The KV-list cadence stays a user decision:** 07-04 classified the retiming as Rule 4 (the due-card math couples `SCAN_TICK_S` into `projectTurnCards`' due rule); this prep carries the three documented options into the gate file and the checkpoint, plus the correction that a 90 s tick is not cron-expressible. - **The kv-expiry live leg is an auth gate, not a skip-failure:** absent CF credentials degrade to exit 2 by design; the manual wrangler listing (`[]`) corroborates the namespace's live state and is recorded in the gate file. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] The fetch( call-site handler misclassified quoted absolute URLs as TS declarations** - **Found during:** SA6's RED phase - **Issue:** `argText.includes(":")` treats any quoted URL's colon as a TS annotation colon, skipping quoted absolute fetch targets entirely — the doc-pointer class could not be narrow - **Fix:** quoted strings are targets; only unquoted colon-carrying args are declarations - **Files modified:** scripts/lib/source-gates.mjs - **Commit:** 2489cca **2. [Rule 1 - Bug] register-commands.mjs sent the wrong Bot API field name (the /cari menu defect's true root cause)** - **Found during:** the deploy canary (default-scope read-back was empty, all_private_chats lacked cari) - **Issue:** `bot_command_scope` is not a Bot API field; Telegram silently ignores it — all four registration calls hit the default scope and the two clear calls cleared the default set - **Fix:** field renamed to `scope` in all six calls; registration re-run; per-scope read-back proven with `getMyCommands` + the correct param; docs corrected - **Files modified:** bot/scripts/register-commands.mjs, docs/KNOWN-LIMITATIONS.md, bot/README.md - **Commit:** 141d6d2 **3. [Rule 3 - Blocking residue] Pre-existing uncommitted work landed (07-04's residue caution + the logo quick task's TBD commit)** - **Found during:** tree prep before deploy - **Issue:** the 07-03 follow-up fixes were described in 07-03-SUMMARY but absent from its commits; the logo replacement was complete but uncommitted (and web/public assets were untracked while start.ts referenced them) - **Fix:** landed as three commits (`7a1206d`, `b7576c1`, `71fff10`) after re-verifying the baselines (bot 126/126, web 165/165, captures 22/22) - **Files modified:** scripts/lib/cloudflare-probes.mjs + source-gates.mjs + test; logo residue set; shell.css + captures --- **Total deviations:** 3 auto-fixed (2 bugs, 1 residue landing) **Impact on plan:** the audit-green state is restored and the launch canaries are genuinely armed; no plan surface was dropped. Task 2 (the physical walk + battery run) and task 3 (STATE.md record) remain. ## Issues Encountered - No Cloudflare API token in the executor's environment (`bot/.dev.vars` carries only BOT_TOKEN/WEBHOOK_SECRET/WEBHOOK_URL) — the kv-expiry live leg stays NOT RUN (exit 2, honest class); exact token-creation steps surfaced at the checkpoint - The stale phase-2/3-era Telegram command sets (all_chat_administrators: `start`; all_private_chats: `help, privacy` without cari) are now genuinely cleared/overwritten by the fixed registration - `npm run capture` needs a >120 s window (Playwright legs) — completed with an extended timeout ## Known Stubs None — the gate file is a protocol by design (every verdict cell starts ⬜ per T-07-27); no product code carries placeholder data. ## User Setup Required **One auth gate (kv-expiry live leg):** create a Cloudflare API token (Account scope: Workers KV Storage → Read) and export `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`, then re-run `npm run privacy-audit -- --live` and `npm run budget-audit -- --live` at gate time. ## Next Phase Readiness - **The gate file is ready to walk:** Part 0 (the automated half — every command exits 0 at prep time, re-run at gate time), Part A (the physical walk — the user's legs), Part B (the battery run — the user's legs), Part D (the signature) - **Two decisions ride with the signature:** the KV-list cadence (options in the gate file) and the kv-expiry credential provision - Baselines: bot 126/126, web 165/165, routing 65/65, search 57/57, privacy suite 13/13, budget suite 19/19, captures 22/22; deploy `dd55a073` --- *Phase: 07-hardening-private-launch* *Completed: 2026-10-25* ## Self-Check: PASSED - `07-LAUNCH-GATE.md` on disk with all six section headings; 32 ⬜ lines; no APPROVED string. - All 7 commits verified in git log (`7a1206d`, `b06c64d`, `2489cca`, `b7576c1`, `71fff10`, `141d6d2`, `53930eb`). - Final suite: bot 126/126, web 165/165, routing 65/65, search 57/57; privacy-audit CLEAN; budget-audit CLEAN. - STATE.md and ROADMAP.md untouched (orchestrator-owned, per the spawn objective). ## Gate resolution (orchestrator, 2025-10-25) LAUNCH GATE **SIGNED** (bac01a9). User acts executed in-gate: Part A walk (pass), battery DEFERRED (v1.x), KV cadence $0-retune (720/day), CF token provisioned, live audits 7/7 + 6/6 CLEAN, brand sweep arah², /cari root-cause fix. Verification: 07-VERIFICATION.md — phase 7 PASSED; **v1 milestone complete**.