10 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created, updated
| phase | slug | status | nyquist_compliant | wave_0_complete | created | updated |
|---|---|---|---|---|---|---|
| 4 | destination-search-kl-gazetteer | final | true | false | 2026-10-23 | 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, ornpm 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) plusnpm 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 frombot/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); thewrangler deploy --dry-runWorker-bundle marker grep lands in 04-03-T2 oncebot.tsimports 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(explicitweb/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
- All tasks have
<automated>verify or Wave 0 dependencies - Sampling continuity: no 3 consecutive tasks without automated verify (every task carries an automated verify; the single checkpoint pairs it with the automated half)
- Wave 0 covers all MISSING references
- No watch-mode flags
- Feedback latency < 15s (excluding plan-scoped capture/dry-run gates)
nyquist_compliant: trueset in frontmatter
Approval: planner-complete (2026-10-23) — statuses flip to ✅ during execution