107 lines
10 KiB
Markdown
107 lines
10 KiB
Markdown
---
|
||
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
|