Files
arah2/.planning/phases/07-hardening-private-launch/07-VALIDATION.md
T
araharah-executor a7d8dbf345 docs(07): revise phase 07 plans — wire the safe-area watch, fold the corpora into budget-audit, add the subrequests row, resolve the research open questions
Checker-issue round 1 on the Phase 7 plan set:

BLOCKER (07-01) — watchSafeAreaChanges and watchFullscreenEvents were created
and unit-tested but never called in production: main.ts:74 was the only
safe-area site and was not in files_modified, and the fullscreen events were
never registered. Insets never re-resolved at runtime on a real device while
every gate stayed green.

  - web/src/main.ts joins 07-01's files_modified; task 4 step 8 wires
    watchSafeAreaChanges(getWebApp()) directly after the boot site's
    applySafeArea(resolveSafeInsets(getWebApp())) line.
  - task 4 step 6 registers watchFullscreenEvents once at the guidance-start
    edge in route.ts, with a deliberately no-op callback (events are
    observability, never correctness).
  - New grep acceptance criteria pin BOTH production call sites; two new
    must_haves key_links name them; T-07-06b now names the boot-site call.

WARNING (07-04 / VALIDATION.md) — the golden corpora were attributed to 07-04
t3, which never ran them. Folded into the budget audit: runGoldenCorpusGate()
re-runs `npm run routing:corpus` and `npm run search:corpus` as child
processes (the same shape runCpuLadderGate already uses), so the ONE
budget-audit command enforces SC4.a/SC4.b. VALIDATION.md rows now name 07-04
t2 and the budget-audit command.

WARNING (07-02 t3) — the landscape-leg snippet omitted the required setup.
The action now says to copy the existing corridor guidance leg VERBATIM
(setup stubGuidanceRoute(page, corridorModel) + guideQuery carrying dest +
fixpair + waitMode/expectGuide) and change ONLY the name, the scheme, and the
viewport; two grep criteria pin the setup and the query.

WARNING (07-04) — D-06/SC4 name subrequests but nothing measured beside the
50/request ceiling. Added runSubrequestClassGate(): a per-request-class
inventory derived from the Worker's route table (engine-A /api/route = 0
external subrequests; the cron tick's Telegram send is the only recurring
class; KV ops tracked separately against their own ceiling). Unmeasurable
counts read "not measured", never 0. docs/BUDGETS.md gains a
"## Subrequests per request class" section and the record states that the
inventory is source-derived, not a live Workers Logs metric.

WARNING (07-RESEARCH.md) — "## Open Questions" is now
"## Open Questions (RESOLVED)" and each of the five items carries its adopted
disposition; Q2/Q3 resolve to "answered by measurement at the launch gate per
D-04/D-06".

INFO (07-VALIDATION.md) — nyquist_compliant flipped to true and the sign-off
is approved 2026-10-25 after the map-row correction.

Waves, dependencies, autonomous flags, and the other three plans are unchanged.
2026-10-25 02:44:30 +08:00

108 lines
8.7 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: 7
slug: hardening-private-launch
status: draft
nyquist_compliant: true
wave_0_complete: false
created: 2026-10-25
---
# Phase 7 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
> Derived from 07-RESEARCH.md `## Validation Architecture`.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | Repo-local Node test harnesses (`node:test`-style): bot/web/routing/search `test/runner.mjs`; Playwright for browser captures in `web` |
| **Config file** | None global — each workspace has `test/runner.mjs`; web `vite.config.ts` |
| **Quick run command** | `npm run web:test && npm run bot:test` |
| **Full suite command** | `npm run bot:test && npm run web:test && npm run routing:test && npm run search:test && npm run search:corpus && npm run routing:corpus && node routing/scripts/measure-route-cpu.mjs` |
| **Phase commands** | `npm run privacy-audit && npm run budget-audit` (new in this phase) |
| **Estimated runtime** | ~60–90 seconds (full suite); audits < 15 s |
---
## Sampling Rate
- **After every task commit:** Run the touched workspace's quick command (`npm test -w web` / `npm test -w bot`); run `npm run privacy-audit` when touching storage/log/network code
- **After every plan wave:** Run the full suite command plus `npm run privacy-audit && npm run budget-audit`
- **Before `/gsd-verify-work`:** Full suite must be green; both audits green; live probes green
- **Max feedback latency:** 90 seconds
---
## Per-task Verification Map
*(Task IDs assigned at planning — 2026-10-25. The `task ID` column names the PLAN TASK that delivers the row; the `Plan` column names the plan file (07-01 … 07-06).)*
| task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 07-01 t3 | 07-01-PLAN.md | 1 | PX-04.a | T-07-06 | Bot API <8.0 or absent method: guidance starts without fullscreen and remains usable | unit | `npm test -w web` | ❌ W0 (`web/test/fullscreen.test.mjs`) | ⬜ pending |
| 07-01 t3 | 07-01-PLAN.md | 1 | PX-04.b | T-07-06 | Bot API 8.0: `requestFullscreen()` called once at guidance start; failure is non-error | unit | `npm test -w web` | ❌ W0 | ⬜ pending |
| 07-01 t3 | 07-01-PLAN.md | 1 | PX-04.c | T-07-06 | `fullscreenChanged` / `fullscreenFailed` update state without throwing | unit | `npm test -w web` | ❌ W0 | ⬜ pending |
| 07-01 t3 | 07-01-PLAN.md | 1 | PX-04.d | T-07-06 | Safe-area inset changes after fullscreen/rotation re-applied | unit | `npm test -w web` | ❌ W0 | ⬜ pending |
| 07-02 t3 | 07-02-PLAN.md | 1 | PX-04.e | — | Landscape viewport keeps banner/list/card/map usable, no overlap/clipping | browser capture | `npm run capture -w web` | Existing harness; ❌ new landscape legs | ⬜ pending |
| 07-06 t2 | 07-06-PLAN.md | 3 | PX-04.f | — | Fullscreen + landscape on Android/iOS/Desktop/browser | manual device | `07-LAUNCH-GATE.md` device matrix | ❌ W0 gate file | ⬜ pending |
| 07-06 t2 | 07-06-PLAN.md | 3 | PX-04.g | — | One Android guidance run ≥30 min records battery, drains <10%/hr | manual measurement | `07-LAUNCH-GATE.md` battery record | ❌ W0 gate file | ⬜ pending |
| 07-02 t2 | 07-02-PLAN.md | 1 | SC2.b | T-07-05 | Route source error/timeout renders designed pending/no-route state | unit/integration | `npm test -w web` | ❌ W0 (`web/test/failure-state-matrix.test.mjs`) | ⬜ pending |
| 07-02 t2 | 07-02-PLAN.md | 1 | SC2.e | T-07-06 | Older Telegram client without 8.0 methods behaves sanely | unit + manual | `npm test -w web`; gate file | ❌ W0 | ⬜ pending |
| 07-03 t1 | 07-03-PLAN.md | 1 | SC3.a | T-07-02 | No banned persistence API in any production `src` tree | source gate | `npm run privacy-audit` | ❌ W0 (`scripts/privacy-audit.mjs`) | ⬜ pending |
| 07-03 t1 | 07-03-PLAN.md | 1 | SC3.b | T-07-01 | Every Worker KV write carries TTL; cron has zero writes | source + runtime | `npm run privacy-audit` | ❌ W0 | ⬜ pending |
| 07-03 t2 | 07-03-PLAN.md | 1 | SC3.c | T-07-01 | Live KV keys TTL-expiring, only expected families exist | live probe | `npm run privacy-audit -- --live` | ❌ W0 | ⬜ pending |
| 07-03 t1 | 07-03-PLAN.md | 1 | SC3.d | T-07-03 | Logs category-only; no coordinates/chat ids/payloads | source + bundle | `npm run privacy-audit` | ❌ W0 | ⬜ pending |
| 07-03 t2 | 07-03-PLAN.md | 1 | SC3.e | T-07-04 | No analytics/beacon; only approved fetch hosts | bundle gate | `npm run privacy-audit` | ❌ W0 | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.a | — | Golden route corpus green (re-run by the budget audit's `golden-corpus` gate) | CLI + suite | `npm run budget-audit` (runs `routing:corpus`); `npm run routing:corpus && npm test -w routing` | ✅ existing | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.b | — | Search golden corpus green (re-run by the budget audit's `golden-corpus` gate) | CLI + suite | `npm run budget-audit` (runs `search:corpus`); `npm run search:corpus && npm test -w search` | ✅ existing | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.c | — | A* CPU p95 ≤8 ms local; live Worker CPU ≤10 ms | measurement | `node routing/scripts/measure-route-cpu.mjs`; live probe | ✅ local; ❌ live probe | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.d | — | Bundle/Worker sizes within recorded budgets | measurement | `npm run budget-audit` | ❌ W0 (`scripts/budget-audit.mjs`) | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.e | T-07-18 | KV writes by request class recorded; cron=0 | source + runtime | `npm run budget-audit` | ❌ W0 | ⬜ pending |
| 07-04 t2 | 07-04-PLAN.md | 2 | SC4.i | T-07-23b | Subrequests per request class recorded; `/api/route` = 0 external; the cron tick's Telegram send is the only recurring class; unmeasurable rows read `not measured` | source + record | `npm run budget-audit` | ❌ W0 | ⬜ pending |
| 07-04 t1 | 07-04-PLAN.md | 2 | SC4.f | T-07-20 | Static assets have intended cache headers | live header probe | `npm run budget-audit -- --live` | ❌ W0 | ⬜ pending |
| 07-05 t2 | 07-05-PLAN.md | 1 | SC4.g | — | Known-limitations doc shipped, linked from `/help` en/ms | unit/doc gate | `npm test -w bot` | ❌ W0 | ⬜ pending |
| 07-05 t1 | 07-05-PLAN.md | 1 | SC4.h | T-07-08 | INFO debts fixed; exact-900s boundary pinned | unit | `npm test -w bot` | ❌ W0 (`bot/test/initdata.test.mjs` 900s vector) | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
*(Assigned to plans at planning — 2026-10-25. Each item names the plan task that creates it.)*
- [ ] `web/test/fullscreen.test.mjs` — covers PX-04.a–d and old-client fallback → **07-01 t3** (FS1–FS9)
- [ ] `web/test/failure-state-matrix.test.mjs` — covers SC2 named vectors and DOM state tokens → **07-02 t2** (FM1–FM7)
- [ ] `scripts/privacy-audit.mjs` + `scripts/lib/*` — consolidated source/bundle/live audit → **07-03 t1–t3**
- [ ] `scripts/budget-audit.mjs` + `scripts/lib/*` — consolidated budget record (CPU ladder, bundle bytes, KV write classes, subrequest inventory, golden corpora) and live probes → **07-04 t2–t3**
- [ ] `web/public/_headers` — hashed-asset immutable caching + HTML revalidation policy, if verified by audit → **07-04 t1**
- [ ] `bot/test/initdata.test.mjs` exact-900s vector — D-07 IN-03 → **07-05 t1** (T4b)
- [ ] `docs/KNOWN-LIMITATIONS.md` — D-08 → **07-05 t2**
- [ ] `.planning/phases/07-hardening-private-launch/07-LAUNCH-GATE.md` — device matrix, battery method/result, checklist signature → **07-06 t1** (protocol) + **07-06 t2** (the human walk)
- [ ] Framework install: none — use the existing custom runners
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Fullscreen + landscape usable on Android/iOS/Desktop real clients | PX-04 (SC1) | No simulated hardware results — honesty protocol | One consolidated walk in `07-LAUNCH-GATE.md`: Android + iOS + Desktop + browser × (boot, search→route, guidance, minimize/resume, trip-end, theme flip, landscape/fullscreen); NOT-RUN legs recorded honestly |
| Battery drain <10%/hr during guidance | PX-04 (SC1) | Real measurement, never synthesized (D-04) | One Android guidance run ≥30 min screen-on; battery % read at start/end; method + caveats written into the gate file |
---
## Validation Sign-Off
- [x] All tasks have `<automated>` verify or Wave 0 dependencies
- [x] Sampling continuity: no 3 consecutive tasks without automated verify
- [x] Wave 0 covers all MISSING references
- [x] No watch-mode flags
- [x] Feedback latency < 90 s
- [x] `nyquist_compliant: true` set in frontmatter
**Approval:** approved 2026-10-25