Commit Graph
143 Commits
Author SHA1 Message Date
araharah-executor f710913a0b test(05-04): extend copy gate for route copy — COPY7 verbatim + floor 40 + COPY5 src/route scope (RED) 2026-10-24 05:25:03 +08:00
araharah-executor 50f22dcbb7 docs(05-02): complete fixture corpus + offline tolerance gate plan 2026-10-24 05:21:59 +08:00
araharah-executor 6c0880b4db feat(05-02): offline corpus tolerance gate — measurePairs core + CLI + in-suite RT1-RT10 (GREEN)
- routing/scripts/route-corpus.mjs: the shared measurement core (exported
  measurePairs) + CLI reporter in the gazetteer-corpus.mjs shape — one
  measurement, two reporters, no drift
- RT1 corpus size 10..20; RT2 schema + RT2b mechanical D-12 proof (every
  committed origin equals its gazetteer landmark within 1e-4 deg); RT3 route
  shape; RT4 probe exclusion (national-museum never a destination); RT5
  haversine-vs-summary band [0.85, 1.10] + bbox+0.05; RT6 speed [1.0, 30] m/s;
  RT7 codec round-trip per route; RT8 ODbL header surfaces; RT9 gz budget
  (measured 16576 B / 102400 B); RT10 alternatives floor (8 pairs / floor 4)
- fully offline: zero network in the measurement path (T-05-05); pair ids and
  measurements printed, never geometry strings (D-15); fixed escalation
  ladder on failure (--only repair -> regenerate -> re-gate)
- suite: 35 passed, 0 failed; npm run routing:corpus exits 0; typecheck clean
2026-10-24 05:19:55 +08:00
araharah-executor 63ef408197 test(05-02): add failing offline corpus tolerance gate suite RT1-RT10 (RED)
- one test per check, mirroring search/test/corpus.test.mjs: the suite imports
  measurePairs from routing/scripts/route-corpus.mjs IN-PROCESS (never spawns
  the CLI) — one measurement, two reporters, no drift
- RED: the measurement core does not exist yet -> 10 named ERR_MODULE_NOT_FOUND
  failures; the committed dataset itself is already green material (task 1)
- fully offline by construction: zero network in the measurement path (T-05-05)
2026-10-24 05:18:16 +08:00
araharah-executor 4e89cff552 feat(05-02): pinned golden-pair corpus + OSRM-demo fetch tool + populated committed dataset (GREEN)
- routing/test/fixtures/golden-pairs.json: the hand-authored 15-pair pinned
  corpus (D-02/D-12) — origins AND destinations are gazetteer landmarks,
  klBbox + 100 KB gz budget + probe id + alternatives floor pinned
- routing/scripts/fetch-route-fixtures.mjs: the maintainer build tool
  (overpass-topup.mjs pattern) — D-12 boundary in the header, pre-flight
  public-coordinate proof against the gazetteer (1e-4 deg), mandatory
  User-Agent, 1200 ms inter-request delay, one retry on network/5xx,
  collect-then-write, --only=<pairId> single-pair repair merge
- routing/src/data/route-fixtures.json: 15 real OSRM-demo pairs committed
  (1-3 verbatim polyline6 routes each; 8 pairs carry >= 2 alternates),
  ODbL header fields preserved (T-05-06)
- loader-env L3 armed (RED commit 01b0ed6) + C6 re-pinned from the empty-seed
  state to the populated corpus (05-01 documented both as 05-02 handoffs)
- npm delegates: root routing:fixtures; routing fixtures + corpus scripts
2026-10-24 05:17:01 +08:00
araharah-executor 01b0ed6884 test(05-02): tighten loader-env L3 to require pair 1 serving at pinned origin (RED)
- L3 previously pinned the 05-01 empty-seed state (honest null at the pinned origin)
- it now asserts the default fixture source SERVES pair 1 (kl-tower-to-kl-sentral)
  at its pinned public origin and resolves honest null ~300 m away (outside the
  200 m tolerance, D-03)
- RED until plan 05-02 populates the committed fixture dataset
2026-10-24 05:14:15 +08:00
araharah-executor 48627d4102 docs(05-03): complete guidance-session contract freeze plan 2026-10-24 05:10:36 +08:00
araharah-executor d5c2bc0938 feat(05-03): freeze guidance_sessions envelope as types-only contract (GREEN)
- GuidanceSessionEnvelope: EXACTLY id, chat_id, route_snapshot, created_at
  (SessionClaim field-freeze law — a 5th property means drift)
- GuidanceRouteSnapshot INLINE (D-07: one key, one TTL, ~2h): destination
  id/name, distanceM/durationS, polyline6 geometry (05-01 storage format),
  startedAtSec resume anchor
- GUIDANCE_SESSION_TTL_SECONDS = 7200 — every Phase 6 write carries it
- guidance:<id> key-scheme doc header; zero runtime behavior (no binding,
  no function, no import — GE4 pins it)

Note: header says 'No KV binding' rather than naming the KV type because
the acceptance gate requires that token to appear NOWHERE in the file,
comments included — same law, gate-clean wording.
2026-10-24 05:09:27 +08:00
araharah-executor 0b8671e9ca test(05-03): add failing guidance-envelope types-only gates (GE1-GE7) (RED)
- GE1 TTL constant === 7200 (~2h, D-06)
- GE2 field freeze: envelope 4 fields / snapshot 6 fields (SessionClaim law)
- GE3 inline snapshot: typed inline, zero snapshotRef tokens in bot/src (D-07)
- GE4 types-only: zero behavior tokens comment-stripped (T-05-08)
- GE5-GE7 negative-space guards: one kv namespace, no Env field, no consumer
  (green by construction — redden only on drift; 05-01 C4/C5/C6 precedent)
2026-10-24 05:08:52 +08:00
araharah-executor 2ee5e4e38b docs(05-01): complete routing workspace plan
- SUMMARY: 3/3 tasks TDD RED->GREEN, 25/25 cases green, 6 commits
- RoutingSource seam + polyline6 codec + fixture backend + loader proof
2026-10-24 05:04:26 +08:00
araharah-executor 3e0c5e3eb4 feat(05-01): geo math + fixture RoutingSource backend + public API
- geo.ts: haversineM + deriveCumulativeDistanceM (D-07 snapped-progress field)
- fixture-source.ts: backend #1 — ORIGIN_TOLERANCE_M=200 matching law,
  honest-null pending semantics (D-03), defensive pair skip at index build,
  lazy memoized index (LAW 1), static JSON import (LAW 2), zero
  logging/storage/network (LAW 3, D-12)
- index.ts: named-export public surface, subpath-with-extension import law
- loader proven in three environments (bare Node 24, esbuild, Vite-family)
2026-10-24 05:03:07 +08:00
araharah-executor 0599ffe14f test(05-01): add failing fixture-source + loader-env suites (F1-F8, L1-L3)
- synthetic injected datasets only — never the network, never the committed file
- F3 pins the 200 m haversine tolerance boundary; F6 the defensive pair skip
- F7 the lazy-index source gate (LAW 1); L2 the esbuild inline marker
2026-10-24 05:02:08 +08:00
araharah-executor 4acb878c33 feat(05-01): polyline6 codec (pure, zero-dependency)
- standard encoded-polyline algorithm at 1e6 precision, double math
  (no int32 shifts) so no coordinate range wraps silently
- decode THROWS on truncated varints — never partial/NaN points (T-05-02)
- round-trip law encode(decode(s)) === s proven on all vectors incl.
  the external live-OSRM geometry (RESEARCH Finding 1)
2026-10-24 05:01:13 +08:00
araharah-executor 9b66f90a4b test(05-01): add failing polyline6 codec vectors (P1-P8)
- hand-derived vectors from the plan's <polyline_vectors> block
- external live-OSRM demo geometry (RESEARCH Finding 1) as ground truth
- P7 pins the truncated-varint refusal law (T-05-02)
2026-10-24 05:00:32 +08:00
araharah-executor c92a955272 feat(05-01): RoutingSource contract + day-one route model + fixture seed schema
- types.ts: the D-01 backend-ladder seam (fixture -> wasm-router -> valhalla),
  D-03 null contract documented on the interface, D-07 day-one fields
  (RouteStep typed + RouteGeometry.cumulativeDistanceM, GUID-05/06 obligation)
- route-fixtures.json: committed EMPTY seed with the ODbL header
  (license/attribution/notice, T-05-06) — plan 05-02 populates it
2026-10-24 04:59:45 +08:00
araharah-executor 69b6a20350 test(05-01): scaffold routing workspace with failing contract suite
- fifth npm workspace @araharah/routing (buildless TS, search/ precedent)
- root delegates routing:test / routing:typecheck / routing:corpus
- C1-C6 RED: contract gates fail while src/types.ts + seed are absent
2026-10-24 04:59:17 +08:00
araharah-executor e5b6c06b12 docs(05): UI-SPEC addendum — card padding convention fixed in live shell 2026-10-24 04:12:51 +08:00
araharah-executor 9ae2059432 fix(shell): card body padding — text sat at card edges (device-gate feedback) 2026-10-24 04:12:19 +08:00
araharah-executor 70e586fd7e docs(05): UI-SPEC approved — checker flags folded (pin tail 8px, exception-class wording, addLayer precision) 2026-10-24 04:06:10 +08:00
araharah-executor f9a140dd84 docs(04): phase 4 PASSED — verification + checkpoint resolution 2026-10-24 03:19:17 +08:00
araharah-executor 280fd68147 docs(04-04): device gate final verdict — PASSED 2026-10-24 03:18:26 +08:00
araharah-executor b427ca071c docs(04-04): /cari menu diagnosis — non-default setMyCommands scopes silently no-op (Telegram-side) 2026-10-24 03:15:11 +08:00
araharah-executor 54a2b54b8b fix(04-04): register-commands arms every shadowing scope — stale all_private_chats set hid /cari (device-gate finding) 2026-10-24 03:08:20 +08:00
araharah-executor 135621f007 feat(04-04): /start carries the araharah logo (static-asset URL, resilient text fallback) + repo icon assets 2026-10-24 02:57:39 +08:00
araharah-executor 2c5fff1899 docs(04-04): device-gate verdicts — legs 1-4+6 PASS, leg 5 FAIL (BotFather Main Mini App missing) 2026-10-24 02:54:33 +08:00
araharah-executor cbe433fef1 feat(04-04): map tilt (pitch 35) + runtime building extrusion reusing style-owned colors — device-gate feedback 2026-10-24 02:53:39 +08:00
araharah-executor 430765c778 docs(04-04): leg-5 carrier note — browser hits the session wall by design; t.me startapp link is the on-device procedure 2026-10-24 02:36:06 +08:00
araharah-executor f33bc76779 docs(04-04): complete webview deep-link hand-off plan — two-carrier resolver + ONBD-03 hooks + device gate escalated 2026-10-24 01:46:39 +08:00
araharah-executor 7b02c7dd4a docs(04-04): the blocking ONBD-02/ONBD-03 device gate — deployed + canaries green, six legs UNVERIFIED pending a real device
- deploy 31138f79 live at https://araharah-bot.araharah-bot.workers.dev;
  MINI_APP_URL == origin; menu button web_app armed; commands
  re-registered (help/privacy/cari); unsigned webhook 401; the live
  app chunk carries 30/30 gazetteer ids + zero crypto tokens
- getWebhookInfo last_error is STALE (2026-10-22, phase-3 era,
  pre-dates both wave deploys; pending 0) — re-read at leg 1
- all six physical legs recorded UNVERIFIED — the phase stays open
  until a real Android/iOS/Desktop walks them; no faked verdicts
2026-10-24 01:44:51 +08:00
araharah-executor 747eeb7b34 test(04-04): the BA7/BA8 drift gates + the search-tree GEO3 pin + the destination-preload capture leg
- BA7: the shipped JS must carry EVERY committed gazetteer id (the
  web half of bot/web id-set parity, T-04-39) — RED against the
  pre-gazetteer bundle, green after the resolver lands in the build
- BA8: the shipped JS carries NO deep-link crypto (JS assets only —
  the official Telegram SDK is external, D-11/T-04-40)
- no-storage: explicit web/src/search memory-only + no-crypto pin
- capture.mjs: the ARAHARAH-DEST signal race + the 'dest' wait mode
  (state=resolved asserted, then the rendered card's text is waited
  on — the card mounts at live, T-04-42) + the shell-dest-preload
  leg; 8/8 expected captures
2026-10-24 01:38:24 +08:00
araharah-executor c2d78b0c8f feat(04-04): ONBD-03 home-screen hooks + the boot wiring + the ?dest= harness profile
- homescreen.ts: wireHomeScreen behind the BOTH-halves Bot API 8.0
  guard; 'added' hides (no nag), 'unknown' (always iOS) shows the
  neutral prompt — indeterminate, never an error; homeScreenAdded
  hides; a refused addToHomeScreen is caught, the affordance stays;
  .btn-secondary in the existing strip — no new furniture; NO
  beforeinstallprompt/manifest/service worker (T-04-38)
- main.ts: locale derivation moved above the bindings block; stage 13
  reads BOTH destination carriers, seeds the memory-only holder, builds
  the destination/unavailable surface (null when no id — Phase 3 boot
  byte-identical, D-12); the live register shows the destination
  surface; the ARAHARAH-DEST state-token signal fires unconditionally
- index.html: ?dest=<id> harness profile threaded to the mock's
  startParam; the block stays inside __ARAHARAH_HARNESS__
- HS1-HS9 green (54/54), web:typecheck green
2026-10-24 01:36:30 +08:00
araharah-executor 6e421e6b59 test(04-04): RED — the ONBD-03 home-screen gate suite (HS1-HS9)
Capability guard (both halves), the four status branches ('added'
hides, 'unknown' shows with the neutral prompt, 'missed' shows,
'unsupported' nothing), tap → addToHomeScreen, homeScreenAdded →
hidden, and the browser-install-API ban. Suite load fails until
web/src/telegram/homescreen.ts exists (RED verified: 45 passed,
1 failed suite-load).
2026-10-24 01:34:44 +08:00
araharah-executor 4f958411f3 feat(04-04): the destination resolver — two carriers, one validation, memory only
- resolve-destination.ts: reads ?dest= (URL, primary — the inline
  web_app-button carrier) then initData's start_param (the signed
  string, secondary — the direct-link/home-screen carrier), ONE
  slug validation, dest_ prefix stripped; resolves against the
  webview's own build-time @araharah/search copy; honest null for
  unknown ids; module-scope memory-only holder (GEO-03/D-07)
- destination-card.ts: the confirmation + honest-unavailable cards,
  createElement/textContent only, existing theme classes (T-04-32)
- webapp.ts: TelegramWebAppLike gains startParam, addToHomeScreen?,
  checkHomeScreenStatus? + HomeScreenStatus (Bot API 8.0+ members)
- telegram-mock.ts: satisfies the extended contract in the same
  change (startParam initData wiring, homeScreenStatus, __homeScreenAdded)
- strings.ts: 5 new copy fields in en+ms; web depends on @araharah/search
- DP1-DP12 green (45/45 web suite), web:typecheck green
2026-10-24 01:33:56 +08:00
araharah-executor 3e3d1917e6 test(04-04): RED — the ONBD-02 destination-preload gate suite (DP1-DP12)
Two carriers, one validation, memory-only resolution against the
webview's own build-time gazetteer copy. Suite load fails until
web/src/search/resolve-destination.ts exists (RED verified: 33 passed,
1 failed suite-load).
2026-10-24 01:31:56 +08:00
araharah-executor 3c790e26de docs(04-03): complete chat-search-surface plan — /cari venue card + alternates + corrected deep-link carrier 2026-10-24 01:23:41 +08:00
araharah-executor 5dcef0e2bb test(04-03): the four CS landmines + the CR1-CR15 /cari gate suite
- commands.test.mjs: commandSources gains commands/cari.ts; CS4 documents
  the SECOND inline-keyboard carve (venue card allowed, reply keyboard
  still banned in commands/); CS5 menu becomes help/privacy/cari; CS5b's
  future-stub ban narrows to /pergi_ke (reserved for Phase 5, D-01); CS6
  registers cari+search in the alias set
- cari.test.mjs NEW: CR1-CR15 — payload shapes captured through the real
  createBot + fake baseFetch (getMe included), plus source-introspection
  gates with the stripComments helper: venue fields, OPEN URL = configured
  origin + plain slug id, NO-CRYPTO law (D-11), one-message alternates,
  callback_data discipline, honest refusal, 128-char cap, always-answer,
  category-only logging (D-15), bilingual copy, registration script, swap
  semantics, hostile-id drop
- register-commands.mjs: menu list gains cari (two fetch calls unchanged)
- [Rule 1] location-fallback.ts: the message handler now passes non-location
  messages through (next) — it matched EVERY message and silently starved
  all downstream message-stage handlers, so /cari never fired through the
  real factory; caught by the fake-transport suite
- suites: bot 65/65 (15 CR + 9 CS + all prior), web 33/33, search 57/57,
  bot:typecheck green
2026-10-24 01:21:10 +08:00
araharah-executor 13e5073261 feat(04-03): the alternates callback handler + the bot factory wiring (D-02)
- search-alternate.ts: scoped alt: callback matcher that answers FIRST
  (30-second rule), validates the id against the slug charset BEFORE the
  lookup (T-04-23), resolves via source.byId() — honest null, never a
  fabricated card; catch-all callback_query:data answers everything else
  so Telegram never spins for 60 s (T-04-27)
- sendVenueCard: the ONE card builder shared by /cari and the swap path —
  first card and swapped card structurally identical; a tap sends a NEW
  sendVenue (no in-place edits — they destroy the venue payload, T-04-28)
- alternatesFor(entry, source): async — awaits search() on the entry's own
  name, excludes the entry, caps at 4 (D-02)
- bot.ts: ONE createGazetteerSource() shared by both registrations (single
  memoized index); origin = env.MINI_APP_URL ?? ''; header names the second
  documented D-02 exception
- cari.ts now calls the shared card builder
- wrangler dry-run inlines the gazetteer (suria-klcc marker in the bundle) —
  completes the L3 bundle-marker half deferred from plan 04-01
2026-10-24 01:15:21 +08:00
araharah-executor a73a38830a feat(04-03): the /cari command + venue card + OPEN deep link (D-01/D-02/D-10/D-11)
- cari.ts: /cari + /search twin, 128-char pre-search cap (T-04-21), honest
  no-match refusal (D-13), single sendVenue card with inline OPEN + alternates
- deep-link carrier corrected per RESEARCH Pitfall 1: web_app button URL
  carries ?dest=<plain slug id> — WebAppInfo has only a url field; no crypto
- origin from configured MINI_APP_URL only (never user input); blank origin
  omits the OPEN button instead of guessing (T-04-30)
- strings.ts: searchUsage/searchNoMatch/searchQueryTooLong/searchOpenButton/
  osmAttribution in en+ms; helpMenu gains the /cari line in both locales
- category-only logging law (D-15): four literal rejection categories, query
  never logged; success path logs nothing
- config: MINI_APP_URL in Env + wrangler vars (public, not secret) +
  .dev.vars.example + docs/CONFIGURATION.md row and sections
- bot deps gain @araharah/search (workspace link)
2026-10-24 01:13:13 +08:00
araharah-executor 3aec9542a3 docs(04-02): complete golden-corpus + bake-off-close plan
- SUMMARY: 3/3 tasks, 5 commits, corpus gate green at 100% top-3 / 7-7 refusals,
  lever None; deviations documented (fixture-source implication, bbox pin,
  pavilion-kl coordinate fix)
2026-10-24 01:05:25 +08:00
araharah-executor 99bc9c9a09 docs(04-02): close the D-14 bake-off — threshold met on the first run, lever None
- docs/SEARCH-QUALITY.md: the bake-off close record — corpus v1 (75 cases) x
  gazetteer v1 (30 entries), measured 2026-10-23T17:02Z via npm run search:corpus
  - top-1 66/68 (97.1%), top-3 68/68 (100.0%) >= 90% threshold
  - negatives refused 7/7, refusal margin 0.643 < 0.72 admission line
  - per-category table (landmark/abbreviation/glued/typo/alias all 100%)
  - escalation lever: None — met on first run; no abbreviations.ts row, no
    alias, no corpus edit; the fixed ladder was never climbed
  - coordinate verification: 10 seed entries spot-checked against Nominatim
    (identifying UA, 1 req/s): 8/10 within 0.01°; bukit-bintang outlier is an
    area-vs-point discrepancy (no change); pavilion-kl was a real ~2 km error
  - both top-1 misses documented as expected disjoint-tier behaviour
- kl-gazetteer.json: pavilion-kl coordinate corrected (3.1478,101.695) ->
  (3.149,101.7132) — Rule 1 data bug found by the prescribed Nominatim
  verification; post-fix delta 0.0002°; corpus is coordinate-blind, all suites green
2026-10-24 01:03:39 +08:00
araharah-executor c6a5f2b008 feat(04-02): Overpass top-up proposer (D-06) + JSON-to-SQLite converter (D-05)
overpass-topup.mjs — maintainer-time repo tool, six hard laws:
- never imported by bot/src or web/src (TP1 gate); Worker reads only the committed JSON
- exactly ONE request per run: both live paths (Overpass POST, Nominatim GET)
  share a single requestJson helper — one fetch( call site (TP7)
- identifying User-Agent araharah-gazetteer-topup/1.0 mandatory (missing UA = HTTP 406)
- busy/saturated/unusable response -> 'Overpass instance busy — retry later', exit 0 (TP4)
- output is a PROPOSAL only; source never names the curated file, so it cannot write it (TP6)
- --source=overpass|nominatim|fixture (--fixture implies fixture), --endpoint/OVERPASS_ENDPOINT
  override, bbox-bounded single-family query pinned verbatim (3.05,101.55,3.25,101.80) (TP3)
- --fixture selects the offline path by itself (a fixture flag silently hitting the
  live network would be a trap — fixed during GREEN)

overpass-response.json — committed real-shape Overpass response (6 KL elements:
nodes with lat/lon, ways/relation with center) so the test is deterministic.

json-to-sqlite.mjs — backend #2's dataset format reachable today (D-05):
- reads the same committed kl-gazetteer.json, emits kl-gazetteer.sqlite via
  node:sqlite (zero added deps) with the SAME id set (CV1), plus the ODbL
  notice embedded in a metadata table + NOTICE sidecar (ODbL s4.4, T-04-20)
- deterministic: rows in dataset order, no timestamps
2026-10-24 01:01:00 +08:00
araharah-executor ec98c8d454 test(04-02): RED — D-06 topup law suite (TP1-TP7) + D-05 converter suite (CV1-CV3)
- TP1 no 'overpass' under bot/src|web/src (never a runtime dependency)
- TP2 identifying User-Agent pinned (missing UA = HTTP 406, live-verified)
- TP3 bbox-bounded query tuple present
- TP4 busy fixture -> exit 0 + 'Overpass instance busy' soft-exit line
- TP5 committed fixture -> PROPOSAL output with name= lines + NEVER-writes law
- TP6 source never names the curated dataset file
- TP7 exactly one fetch( call site (shared helper for both live paths)
- CV1 converter preserves the gazetteer id set exactly (read back via node:sqlite)
- CV2 converter reads the committed kl-gazetteer.json
- CV3 no sqlite/json-to-sqlite under bot/src|web/src

8 cases verified failing (scripts absent) before GREEN.
2026-10-24 00:56:08 +08:00
araharah-executor b2689f71b1 feat(04-02): pinned golden corpus + D-14 corpus runner + Nyquist gate
- golden-corpus.json: 75 hand-written pinned cases (D-13) — landmark 17 /
  abbreviation 15 / glued 7 / typo 14 / alias 15 / negative 7, incl. two
  near-miss negatives (Penang 0.625, Kampung Cina ~0.58) below the 0.72
  admission line; threshold.top3_hit_rate pinned at 0.9
- gazetteer-corpus.mjs: the D-14 runner — imports the SAME src/index.ts the
  bot uses, measures top-1/top-3 + per-category rates + avg ms/query, prints
  the refusal margin and the fixed escalation ladder on failure, exports
  measureCorpus() for the in-process suite, exits 0 only on threshold + zero leaks
- corpus.test.mjs goes green: CO1-CO8 (the Nyquist gate inside npm run search:test)
- delegates: search:corpus (root) + corpus (search workspace)

Measured: top-3 68/68 (100.0%) >= 90%, negatives refused 7/7, margin 0.643 < 0.72 —
threshold met on the first run (D-14 lever: None).
2026-10-24 00:54:47 +08:00
araharah-executor dea7971358 test(04-02): RED — golden-corpus D-14 gate suite (CO1-CO8)
- CO1 top-3 >= corpus threshold (D-14), measured in-process via the runner's measureCorpus
- CO2 zero negative leaks (honest refusal, D-13)
- CO3 tautology floor: >= 15 non-negative queries not string-equal to any alias/name
- CO4/CO5 every expectId exists in the gazetteer + start_param-safe charset
- CO6 size >= 60 + per-category floors (landmark 12 / abbreviation 15 / glued 6 / typo 12 / alias 12 / negative 6)
- CO7 per-category rates printed so suite and runner output agree
- CO8 refusal margin pinned: every negative's best two-level fuzzy sim < 0.72, one near-miss in [0.55, 0.72)

All 8 cases verified failing (ENOENT corpus fixture) before GREEN.
2026-10-24 00:52:33 +08:00
araharah-executor 2e724cbee1 docs(04): commit plan-revision residue + 04-01 SUMMARY 2026-10-24 00:39:32 +08:00
araharah-executor 8b6ad2047f docs(04-01): complete shared-search-module plan
- 3 tasks executed RED→GREEN (6 commits): workspace scaffold + JUPEM
  normalizer, OSA distance + disjoint-tier scorer, curated 30-entry KL
  gazetteer + lazy three-environment loader + GPL/ODbL licensing
- 39/39 suite green; typecheck clean; esbuild inlines the dataset;
  wrangler dry-run exit 0; bot + web suites unaffected
- SRCH-01/SRCH-02 foundation proven: KLCC→suria-klcc, negatives refused
2026-10-24 00:38:58 +08:00
araharah-executor 8579da9d71 feat(04-01): curated KL gazetteer + lazy three-environment loader + GPL/ODbL licensing
- kl-gazetteer.json: 30 hand-curated KL-metro landmarks (10 required + 20
  more) with locals' aliases as native data (D-08) — canonical OSM names
  as name, shorthand ('KLCC', 'DUKE', 'Sg Wang') as aliases — plus the
  ODbL-1.0 attribution header traveling inside the JSON (D-06/D-07)
- gazetteer.ts: the three laws — lazy memoized index never at module
  scope (Workers startup, T-04-04), static JSON import surviving
  wrangler/Vite/bare-Node (D-07), zero logging/storage (D-15); aliases
  normalized once at index-build (D-09 symmetry)
- source.ts: the D-03 SearchSource — async search() + synchronous byId()
  (slug-validated, honest null); tiered admission (fuzzy only at sim
  >= 0.72) so 'Kuching'/'Penang' refuse as [] — never a fabricated pin
- index.ts: the named-export public API (no default, subpath+extension
  imports per the @araharah/theme precedent)
- LICENSE: the verbatim GPL-3.0 text (D-04); LICENSES.md: the dual-licence
  note (code GPL-3.0, data ODbL-1.0 + OSM attribution); README: corrected
  licence section + search/ in the layout, four-package table, commands

G1-G11 + L1-L3 green (39/39); typecheck clean; esbuild bundle carries the
suria-klcc marker; wrangler dry-run exits 0; bot + web suites unaffected.
2026-10-24 00:36:03 +08:00
araharah-executor f1b31ca7ed test(04-01): RED — gazetteer schema/license gates (G1-G11) + three-environment loader proof (L1-L3)
- G1-G6: dataset invariants — schema, slug-id uniqueness, canonical-name
  law ('KLCC' is an alias, never a name), DUKE-as-alias, KL bbox, the
  ODbL header traveling inside the JSON
- G7-G9: the licensing gate — verbatim GPL-3.0 LICENSE, LICENSES.md,
  the README no longer claiming 'all rights reserved'
- G10-G11: no logging/storage anywhere in the module (D-15); the loader
  builds its index inside a function, never at module top level
- L1-L3: the phase's top mechanical risk — bare-Node loading with the
  SRCH-01/negative probes, the static JSON import law, esbuild INLINING
  the dataset + the wrangler dry-run build guard

13 cases fail while the dataset, loader, LICENSE files, and README fix
are absent — the RED gate.
2026-10-24 00:32:57 +08:00
araharah-executor 6368bad3d5 feat(04-01): OSA Damerau distance + disjoint-tier scorer (the residual net)
- distance.ts: hand-rolled 3-band OSA, adjacent transposition = 1 edit,
  zero imports (D-07); the row rotation is pinned by the D1-D9 vectors
- score.ts: TIER bands strictly disjoint descending — EXACT 1.00 /
  ALIAS_EXACT 0.90 / PREFIX 0.75 / TOKEN_SET 0.65 / FUZZY as the 0.60
  CEILING (a fuzzy score is fuzzySim * 0.60, never a 0.00 multiplier);
  FUZZY_ADMIT_SIMILARITY = 0.72 exported for source.ts + the corpus
  runner; token-level residual lets 'Sentrl' reach 'KL Sentral' while
  'Jln Tun Razak' cannot reach 'Jalan Ampang'
- scoreCandidate returns the RAW matched alias (corpus provenance);
  an exact ALIAS hit rides ALIAS_EXACT so a name-exact outranks it
- score.test.mjs S7 corrected during GREEN: admission belongs to
  source.ts — scoreCandidate asserts the sub-floor similarity instead

D1-D9 + S1-S7 green; typecheck clean.
2026-10-24 00:32:44 +08:00
araharah-executor 25ee1e9c61 test(04-01): RED — OSA distance vectors (D1-D9) + disjoint-tier scorer suite (S1-S7)
- 9 hand-computed Damerau-Levenshtein vectors (transpositions, empties,
  kitten/sitting sanity, the corpus's klcc/kclc + sunway/sunwya typos)
- 7 scorer laws: tier literals + disjointness, min-length guard before
  exact, exact-outranks-fuzzy, mid-word PREFIX vs token-boundary
  TOKEN_SET, fuzzy residual 0.75*0.60 arithmetic, the 0.72 admission
  boundary (Penang/Padang 0.667 refuses), matchedAlias raw provenance

All 16 new cases fail while src/distance.ts + src/score.ts are absent.
2026-10-24 00:25:19 +08:00