Files
arah2/.planning/phases/04-destination-search-kl-gazetteer/04-VALIDATION.md
T

107 lines
10 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: 4
slug: destination-search-kl-gazetteer
status: final
nyquist_compliant: true
wave_0_complete: false
created: 2026-10-23
updated: 2026-10-23
---
# Phase 4 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | Existing hand-rolled harnesses: `bot/test/runner.mjs`, `web/test/runner.mjs`; plan 04-01 adds the fourth workspace's identical pair (`search/test/runner.mjs` + `search/test/harness.mjs`, copied verbatim) — no new framework; the corpus runner is both a script (`search/scripts/gazetteer-corpus.mjs`) and an in-process suite (`search/test/corpus.test.mjs`) |
| **Config file** | None — suites are discovered by filename convention (`*.test.mjs`) per workspace |
| **Quick run command** | `npm run search:test` / `npm test -w bot` / `npm test -w web` |
| **Full suite command** | `npm run search:test && npm test -w bot && npm test -w web && npm run audit -w theme` + both typechecks |
| **Corpus gate command** | `npm run search:corpus` (the D-14 bake-off close; also asserted in-process by `search/test/corpus.test.mjs` so `npm run search:test` fails on a corpus regression) |
| **Estimated runtime** | ~15 seconds (the hand-rolled runners are sub-second; the corpus run is ~2500 score comparisons; the capture harness is the slow leg and is plan-scoped) |
---
## Sampling Rate
- **After every task commit:** Run the touched workspace's suite (`npm run search:test`, or `npm test -w bot` / `npm test -w web`) plus that workspace's typecheck.
- **After every plan wave:** Run the full suite (`npm run search:test && npm test -w bot && npm test -w web`) **plus** `npm run search:corpus` — the corpus must be ≥ threshold at every wave merge, not just at the end (this is what makes the D-14 escalation ladder actionable rather than a last-minute scramble).
- **Before `/gsd-verify-work`:** Full suite green + corpus gate green.
- **Max feedback latency:** 15 seconds (excluding the plan-scoped capture harness and the wrangler dry-run proof, which are per-plan gates, not per-commit gates).
---
## Per-task Verification Map
| task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 04-01-T1 (workspace + types + JUPEM table + normalizer) | 01 | 1 | SRCH-02, D-09 | T-04-01/02/08 | no logging/storage in the module; table is DATA; symmetric normalization | unit | `npm run search:test` | ❌ W0 | ⬜ pending |
| 04-01-T2 (OSA distance + tiered scorer) | 01 | 1 | SRCH-02 | T-04-03/05 | min-length guard; disjoint tiers; fuzzy admission ≥ 0.72 | unit | `npm run search:test` | ❌ W0 | ⬜ pending |
| 04-01-T3 (gazetteer + loader + source + LICENSE/ODbL) | 01 | 1 | SRCH-01, D-04/06/07/15 | T-04-04/06/07/09/11 | honest refusal (`[]`); dataset invariants; GPL+ODbL notices; lazy index; 3-environment loader proof (esbuild search-module bundle marker at wave 1; wrangler dry-run = exit-0 build guard) | unit + bundle | `npm run search:test` + `node_modules/.bin/esbuild search/src/index.ts --bundle` marker grep + `npx wrangler deploy --dry-run` exit 0 (in `bot/`) | ❌ W0 | ⬜ pending |
| 04-02-T1 (golden corpus + D-14 runner + Nyquist gate) | 02 | 2 | SRCH-01/02, D-13/14 | T-04-12/13/19 | pinned corpus; threshold 0.9 un-editable; zero negative leaks | corpus | `npm run search:corpus` + `npm run search:test` | ❌ W0 | ⬜ pending |
| 04-02-T2 (Overpass top-up + JSON→SQLite converter) | 02 | 2 | D-05/06 | T-04-14/15/16/17/20 | repo tools never runtime deps; UA mandatory; soft-exit on busy; id set preserved | fixture-driven | `npm run search:test` | ❌ W0 | ⬜ pending |
| 04-02-T3 (bake-off close + SEARCH-QUALITY.md + coord verification) | 02 | 2 | D-14 | T-04-13 | measured, recorded, honest decision; one named lever | record | `npm run search:corpus` | ❌ | ⬜ pending |
| 04-03-T1 (/cari + venue card + OPEN deep link + MINI_APP_URL) | 03 | 2 | SRCH-03, D-01/02/10/11/15 | T-04-21/22/24/30 | 128-char cap; category-only logs; configured origin | source-gate + typecheck | `npm run bot:typecheck` + `npm test -w bot` | ❌ W0 | ⬜ pending |
| 04-03-T2 (alternates callback handler + bot factory wiring) | 03 | 2 | SRCH-03, D-02 | T-04-23/27/28 | id validated before lookup; every callback answered; no editMessageText; Worker-bundle marker grep (deferred from 04-01-T3 — `bot.ts` now imports the source) | integration | `npm test -w bot` + `npx wrangler deploy --dry-run` marker grep (in `bot/`) | ❌ W0 | ⬜ pending |
| 04-03-T3 (commands.test.mjs landmines + cari.test.mjs gates) | 03 | 2 | SRCH-03, D-11/15 | T-04-25/26/29 | no crypto in deep-link path; no-match refusal honest; no query in logs | gates | `npm test -w bot` | ❌ W0 | ⬜ pending |
| 04-04-T1 (two-carrier destination resolver + mock + strings) | 04 | 3 | ONBD-02, D-11 | T-04-32/33/34/35 | both carriers one validation; honest null; memory-only; no crypto | unit | `npm test -w web` | ❌ W0 | ⬜ pending |
| 04-04-T2 (ONBD-03 hooks + boot wiring + `?dest=` profile) | 04 | 3 | ONBD-03, D-12 | T-04-36/37/38 | Bot API 8.0 guard; "unknown" ≠ error; no browser install APIs | unit | `npm test -w web` | ❌ W0 | ⬜ pending |
| 04-04-T3 (BA7/BA8 drift gates + destination-preload capture) | 04 | 3 | ONBD-02 | T-04-39/40/41/42 | gazetteer inlined in bundle; no crypto shipped; capture asserts state=resolved + card text | integration + capture | `npm test -w web` + `npm run capture -w web` | ✅ extend | ⬜ pending |
| 04-04-T4 (device gate — BLOCKING checkpoint) | 04 | 3 | ONBD-02/03 | — | real-device deep-link + home-screen verification; honest UNVERIFIED if no device | manual (checkpoint) | automated half: `npm test -w bot && npm test -w web && npm run search:corpus` | ❌ exit gate | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
All new this phase (the repo has no search infrastructure today). The owning tasks are marked; a task's `<verify>` may reference a suite created in the SAME task (RED-first per the repo's TDD convention).
- [ ] `search/test/runner.mjs` + `search/test/harness.mjs` — the fourth workspace's harness pair, copied verbatim from `bot/test/` (04-01-T1)
- [ ] `search/test/normalizer.test.mjs` — per-rule vectors (D-09: every table rule), symmetry, whole-token-only, glued split, Presint/Prsn, idempotence (04-01-T1)
- [ ] `search/test/distance.test.mjs` — hand-computed OSA vectors (04-01-T2)
- [ ] `search/test/score.test.mjs` — tier disjointness, min-length guard, exact-outranks-fuzzy, admission boundary (04-01-T2)
- [ ] `search/test/gazetteer.test.mjs` — entry schema, unique slug ids, KL bbox, ODbL header, LICENSE presence, no-logging/no-storage, lazy loader (04-01-T3)
- [ ] `search/test/loader-env.test.mjs` — bare-Node import + static-import assertion + esbuild search-module bundle marker (`suria-klcc`); the `wrangler deploy --dry-run` Worker-bundle marker grep lands in 04-03-T2 once `bot.ts` imports the source (04-01-T3 + 04-03-T2)
- [ ] `search/test/fixtures/golden-corpus.json` — the pinned D-13 corpus, 60+ hand-written cases (04-02-T1)
- [ ] `search/scripts/gazetteer-corpus.mjs` + `search/test/corpus.test.mjs` — the D-14 runner and its in-suite Nyquist gate (04-02-T1)
- [ ] `search/scripts/overpass-topup.mjs` + `search/test/topup.test.mjs` + `search/test/fixtures/overpass-response.json` — the D-06 tool, proven offline (04-02-T2)
- [ ] `search/scripts/json-to-sqlite.mjs` + `search/test/converter.test.mjs` — the D-05 converter (04-02-T2)
- [ ] `bot/test/cari.test.mjs` — CR1–CR15 payload/structural gates (04-03-T3)
- [ ] `web/test/destination-preload.test.mjs` — DP1–DP11 two-carrier gates (04-04-T1)
- [ ] `web/test/homescreen.test.mjs` — HS1–HS9 capability-guard gates (04-04-T2)
- [ ] **Extend (not new):** `bot/test/commands.test.mjs` (CS4 carve, CS5 menu, CS5b narrowed stub ban, CS6 alias set), `web/test/build-artifact.test.mjs` (BA7 inlining, BA8 no-crypto), `web/test/no-storage.test.mjs` (explicit `web/src/search/` case)
*The corpus runner is the phase's primary acceptance instrument (D-14 thresholds) — it is both a script and an in-suite gate, so `npm run search:test` fails whenever the corpus regresses.*
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Venue card renders + OPEN IN ARAHARAH launches Mini App preloaded (ONBD-02) | SRCH-03, ONBD-02 | Requires real Telegram client + deployed Worker (Bot API cannot be simulated locally end-to-end) | `/cari KLCC` in chat → venue card appears → tap OPEN → webview opens aimed at the destination (04-04-T4, legs 1-3) |
| Home-screen launch lands search-ready (ONBD-03) | ONBD-03 | PWA install is a device-OS interaction | Add to home screen → launch → app boots into search-ready map (04-04-T4, leg 4) |
| Deep-link carrier behavior on real clients | ONBD-02 | The carrier mechanism (URL query primary, start_param secondary) must be observed on live Telegram Android/iOS | Open the web_app button from the inline keyboard on both platforms; verify destination preload (04-04-T4) |
| Honest unknown-id + honest refusal on device | D-13 | Client-rendering truth | `?dest=not-a-real-place` → "Destination unavailable"; `/cari Kuching` → text refusal, no pin (04-04-T4, legs 5-6) |
All four are consolidated in plan 04-04's blocking checkpoint (task 4), which records explicit PASS/FAIL/UNVERIFIED verdicts in `04-DEVICE-GATE.md` — an UNVERIFIED device leg holds the phase open with the blocker in `.planning/STATE.md`, never a silent close (the Phase 3 precedent).
---
## Validation Sign-Off
- [x] All tasks have `<automated>` verify or Wave 0 dependencies
- [x] Sampling continuity: no 3 consecutive tasks without automated verify (every task carries an automated verify; the single checkpoint pairs it with the automated half)
- [x] Wave 0 covers all MISSING references
- [x] No watch-mode flags
- [x] Feedback latency < 15s (excluding plan-scoped capture/dry-run gates)
- [x] `nyquist_compliant: true` set in frontmatter
**Approval:** planner-complete (2026-10-23) — statuses flip to ✅ during execution