Commit Graph
100 Commits
Author SHA1 Message Date
araharah-executor d0751c1a33 feat(05-04): route strings (10 keys en+ms) + @araharah/routing workspace dep (GREEN)
- 10 ShellStrings keys per the binding UI-SPEC copy table (routeRegister,
  routeDiffFastest/Slower templates, pending card quartet, ctaSearchAgain,
  ariaRouteAlternatives/Chip templates) — verbatim en + ms
- copy gate: COPY1 floor 40 + route-key pins, COPY5 scans web/src/route,
  COPY7 pins the verbatim pending/CTA/template copy
- web depends on @araharah/routing (lockfile settled)
2026-10-24 05:25:37 +08:00
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
araharah-executor a9f9ab380c feat(04-01): search contracts + JUPEM abbreviation table as DATA + symmetric normalizer
- types.ts: GazetteerEntry / SearchQuery / SearchHit / SearchSource (D-03/D-07/D-08)
  — byId() is separate and synchronous; empty search() array = honest refusal
- rules/abbreviations.ts: the vendored JUPEM table (49 rows incl. Presint→Prsn
  vowel-drop and MS 2039 A3 Kpg forms) as a ReadonlyArray of pairs, inverted once
  by a single generic builder — extends by file edit, never a code change (D-09)
- normalize.ts: NFKD fold → punctuation collapse (keeps '/') → whole-token
  expansion (canonical = long form) → longest-first glued split (remainder >= 3)

T1-T9 green; typecheck clean.
2026-10-24 00:23:37 +08:00
araharah-executor ebcd542643 test(04-01): RED — search workspace scaffold + normalizer vector suite (T1-T9)
- add the search/ npm workspace (root workspaces + search:test/search:typecheck delegates)
- zero-dependency runner/harness copied from the bot/test precedent
- 9 named normalizer laws: rule vectors, symmetry, whole-token-only,
  slash preservation, glued split, vowel-drop, idempotence, folding,
  table-is-data (>= 47 rows, one generic builder)

All 9 cases fail while src/normalize.ts is absent — the RED gate.
2026-10-24 00:21:59 +08:00
araharah-executor c9458add47 docs(04): plan revision 2 — 04-03 contract wording fixes (inline-card sequencing, async alternatesFor) 2026-10-24 00:14:02 +08:00
araharah-executor 4a83ec6871 docs(04): plan revision 1 — fix wave-1 bundle proof + acceptance-criteria corrections 2026-10-24 00:02:27 +08:00
araharah-executor c0134c7e99 docs(04): pattern map — analogs + conventions for every phase-4 surface 2026-10-23 23:40:46 +08:00
araharah-executor a575ecd139 docs(04): create phase plan — 4 plans in 3 waves (search module, corpus/bake-off, /cari surface, webview hand-off + device gate) 2026-10-23 23:40:41 +08:00
araharah-executor aa34e7b50d docs(04): research verification pass — track .planning/, Overpass UA correction, deep-link MTProto confirmation 2026-10-23 23:40:41 +08:00
araharah-executor 7994c84a40 docs(04): research phase domain — destination search + KL gazetteer
Phase 4 research: in-Worker SearchSource over a curated KL gazetteer.

Two critical platform findings:
- Telegram start_param is NEVER delivered to inline web_app buttons
  (direct links + attachment menu only). The destination id must ride
  as a URL query param on the button's URL; D-11's substance preserved.
- beforeinstallprompt never fires in a WebView. ONBD-03 must use
  Telegram's native WebApp.addToHomeScreen(); iOS status is always
  "unknown" by platform design.

Benchmarked the CPU budget: naive full-corpus Damerau is 52ms at
D-07's max scale vs the 10ms Workers Free limit. Exact-hash-first +
token-prefiltered residual is 0.0003ms.

Also: Phase 2 CS3/CS4/CS5 gates need declared amendments; ODbL
attribution obligation for the OSM-derived gazetteer.
2026-10-23 22:50:13 +08:00
araharah-executor 275879a18b ambek .planning 2026-10-23 22:37:55 +08:00
araharah-executor e512104d5e assets: add arah² brand assets — logo + PSD source; display logo in README header 2026-10-23 21:10:00 +08:00
araharah-executor 91ad0c9d93 docs: generate + verify project documentation — canonical docs, per-package READMEs, verified fix pass
- New: docs/ARCHITECTURE.md, docs/GETTING-STARTED.md, docs/DEVELOPMENT.md,
  docs/TESTING.md, docs/CONFIGURATION.md (colored mermaid diagrams, quoted labels)
- Regenerated README.md (arah² / arah2bot naming, canonical clone URL)
- New per-package READMEs: theme/, bot/, web/
- Verified 13 docs against the live codebase (2 fix iterations + 2 residual
  corrections); corrected 56 stale claims across AGENTS/DESIGN/IDEAS/GSD_FIX
2026-10-23 20:56:40 +08:00
araharah-executor af3edefbc6 add sume 2026-10-23 19:18:43 +08:00
araharah-executor 1890b5b1ca docs(03): verification exit addendum — probe removal + GEO-03 platform allowlist 2026-10-23 18:53:28 +08:00
araharah-executor 8a5c8b415f fix(03-04): GEO-03 probe platform allowlist — official telegram-web-app.js writes one sessionStorage bookkeeping key; app surfaces stay empty 2026-10-23 18:52:59 +08:00
araharah-executor 2589e2126f docs(03-04): checkpoint resolution — device matrix passed 2026-10-23 18:45:15 +08:00
araharah-executor 9d6fdc64be docs(03): device matrix PASSED + phase verification 2026-10-23 18:45:15 +08:00
araharah-executor fc99745672 chore(03-04): remove the device-matrix diagnostic probe — exit obligation, matrix passed 2026-10-23 18:41:41 +08:00
araharah-executor 28045d9b9e docs(03-04): log round 7 — probe readings; version-gate the settings CTA 2026-10-23 01:44:14 +08:00
araharah-executor 19a51e5882 fix(03-04): LOCATION SETTINGS CTA version-gated 8.0+ — hides the no-op off-Telegram (Chrome SDK stub reports 6.0) 2026-10-23 01:43:34 +08:00
araharah-executor e64cf7003b docs(03-04): log debug round 6 — signature-field HMAC erratum (the live blocker) 2026-10-23 01:34:53 +08:00
araharah-executor 22fda6d12a fix(03-04): HMAC data-check-string excludes only hash — signature field participates (real clients rejected as tampered; erratum M6) 2026-10-23 01:34:19 +08:00
araharah-executor c062ebaee1 docs(03-04): log debug round 5 — diagnostic probe armed 2026-10-23 01:25:35 +08:00
araharah-executor f4453996ac fix(03-04): on-screen diagnostic probe — WA version/platform, initData char-count, session + location categories 2026-10-23 01:24:52 +08:00
araharah-executor de07c99e8a docs(03-04): log debug round 4 — settings CTA on the wrong instance 2026-10-23 01:19:41 +08:00
araharah-executor 38ce59b1ca fix(03-04): LOCATION SETTINGS CTA on the denied card (was only on awaiting-pin instance — patch-order bug) 2026-10-23 01:19:10 +08:00
araharah-executor caeb9f9729 docs(03-04): log debug round 3 — telegram-web-app.js integration erratum 2026-10-23 01:15:10 +08:00
araharah-executor 3793741d2f fix(03-04): load telegram-web-app.js in head — production WebApp was never defined (no initData, no LocationManager, no BackButton) 2026-10-23 01:14:27 +08:00
araharah-executor ad4d18b180 docs(03-04): UI-SPEC amend — third denied CTA (LOCATION SETTINGS) as debug-round-2 amendment 2026-10-23 01:07:50 +08:00
araharah-executor 89268074a6 docs(03-04): log debug round 2 — LocationManager openSettings deep-link round 2026-10-23 01:07:27 +08:00
araharah-executor 9fa4acb319 fix(03-04): Telegram LocationManager deep-link — LOCATION SETTINGS CTA on denied cards + locationManagerUpdated chain re-run 2026-10-23 01:06:52 +08:00
araharah-executor fff865f6cd docs(03-04): log mid-gate debug round 1 (menu button + dead CTAs) 2026-10-23 01:01:20 +08:00
araharah-executor b1d8768866 fix(03-04): web_app menu button on deployed origin + wire CTA actions on both denied-shaped cards 2026-10-23 01:00:27 +08:00
araharah-executor fc183216c3 docs(03-04): complete one-deployable architecture plan — SUMMARY (checkpoint armed) 2026-10-23 00:46:44 +08:00
araharah-executor 5c155e393d feat(03-04): Mock-Telegram runner page + Playwright capture harness — 7/7 captures + GEO-03 probe
- runner page: ?mock/?scheme/?pin/?locale profile block wholly inside if (__ARAHARAH_HARNESS__) — installer + /api/session + /api/location + /api/location-fallback fetch stubs dead-code-eliminated from production; main.ts import stays outside the guard
- harness-only ?sessiondelay/?fixdelay boot-stage photograph knobs (localhost resolves the chain in ms — staged BOOT-01/BOOT-02 cards need a hold)
- capture.mjs extends theme/scripts/screenshot.mjs: MIN_BYTES, WR-05 docroot guard, READY-signal race, dist-harness docroot, DENIED_LEG_TIMEOUT_MS=90000 for LINK 3's 60s pre-denial poll, timeout = hard failure with a signal diagnostic
- 7 captures: boot-1/boot-2 stages, live light+dark (theme-slot attribute asserted), Desktop denied card w/ ASK VIA CHAT, chat-pin relay LIVE, DSGN-02 side-by-side vs navigate_map_overview
- GEO-03 runtime probe: localStorage/sessionStorage/cookies/IndexedDB asserted empty after boot-to-live
- mock fixes (Rule 1): LocationManager delivery now uses the Telegram wire shape {latitude,longitude,horizontal_accuracy,course} (authoring shape mapped to undefined → NaN LngLat pageerror); languageCode→initData so localeFromInitData parses the real wire format

CAPTURE OK — 7/7 non-blank captures; GEO-03 probe PASSED
2026-10-23 00:37:58 +08:00
araharah-executor 8b39ea70c3 feat(03-04): Worker [assets] binding + one-command deploy + D-14 build-artifact gate
- wrangler.jsonc assets block serves ../web/dist; run_worker_first keeps /api/* + /telegram/* on the Worker (D-13)
- root npm run deploy = build theme → web → bot wrangler deploy + set-webhook (D-15); dev = build + wrangler dev --port 8787 --local (D-12)
- vite.config.ts dual-build: __ARAHARAH_HARNESS__ define keyed on --mode harness, separate dist/dist-harness outDirs
- main.ts guard unchanged; ambient constant declared once in web/src/vite-env.d.ts
- build-artifact.test.mjs BA1..BA6: zero CDN refs, self-hosted fonts, MapLibre worker, inlined style, no mock installer in ANY emitted file (HTML included)
2026-10-23 00:15:26 +08:00
araharah-executor d573acc583 feat(03-03): status strip, state cards, recenter control, boot choreography — map-first, D-09 staged states
- strip.ts: six-state lamp+word left cluster, ±M / FIX Ns AGO telemetry (U+00B1 glyph), elapsed-seconds for acquiring
- cards.ts: boot/denied/lost builders from theme classes, machine-blink gantry (500ms JS duty cycle), GEO/SYS registers from strings.ts, D-06 CTA inversion (chat-fallback=btn-primary, retry=btn-secondary)
- recenter.ts: 40x40 plate w/ >=44 hit slop, orthogonal inline-SVG crosshair, three data-attribute notch states, no inline handlers
- main.ts: boot order map->session->chain (bootMap before acquireFix), null-map guards on every map consumer, ARAHARAH-LIVE emitted outside the guard, data-map=absent fallback, localeFromInitData, ASK VIA CHAT w/ D-03 revalidation, 1s tick re-running the chain at 45s staleness
- map.ts: attribution control moved bottom-left (UI-SPEC flush chip, compact)
- shell.css: attrib chip #map-scoped override (wins cascade over maplibre css), strip left cluster, recenter z-index
- vite.config.ts: define __ARAHARAH_HARNESS__ keyed on --mode harness (bare ambient global would ReferenceError in plain dev; Rule 3)
- session.test.mjs: SESS1-4 memory-token gates
2026-10-23 00:08:34 +08:00
araharah-executor c437d312f6 feat(03-03): Rams shell stylesheet + MapLibre v6 bootstrap, position marker, follow controller
- shell.css: token-only chrome (zero hex, zero transition/animation), dark-slot activation, machined geometry (8px lamp/notch, 40x40 plate, data-map=absent field)
- map.ts: setWorkerUrl + ?worker&url, build-time-inlined liberty-rams.json, GPUInitializationError -> null gate, RAMS-BASEMAP-READY signal
- marker.ts: dot + accuracy circle (100m display cap) + heading cone hidden on null, DOM Marker SVG namespace discipline
- follow.ts: 96px camera padding, 400ms flyTo, user-gesture-only free mode
- UI-SPEC: planner amendments verified (Pitfall 4 present; Open Question 1 appended)
2026-10-22 23:53:18 +08:00
araharah-executor 83985c4a09 feat(03-03): copy module — UI-SPEC table verbatim (en+ms) + COPY1..6 copy gate
- web/src/i18n/strings.ts: 25-key ShellStrings, pickLocale verbatim from bot, localeFromInitData over the raw initData string (RESEARCH §Security V2)
- web/test/copy-gate.test.mjs: parity, content, fallback, no-inline-copy source-introspection (comments stripped), initData locale derivation
2026-10-22 23:51:05 +08:00
araharah-executor 4e4b75da00 docs(03-02): complete location chain core + Mock-Telegram harness plan — SUMMARY
4 tasks, 17 test cases green (GEO-01/02/03, PX-02), typecheck clean; 3 auto-fix
deviations documented; no STATE/ROADMAP writes (orchestrator-owned)
2026-10-22 23:47:47 +08:00
araharah-executor 2659b7f352 feat(03-02): D-11 Mock-Telegram harness + the four requirement gates (17 cases green)
- telegram-mock.ts: fake window.Telegram.WebApp injected into the SAME window
  (never an iframe — Bot API 10.2 origin restriction, Pitfall 6); Web-Crypto
  dev signer mirroring bot/src/security/initdata.ts's two-step HMAC (async
  makeDevInitData — node:crypto stays out of the browser bundle path);
  controllable LocationManager stream, themeChanged emitter, BackButton clicks
- location-chain.test.mjs (10 cases): GEO-01 version gate / null-WebApp guard
  / null-denial / hard timeout / stale-detector + one-read nudge; GEO-02 pin
  pickup / bounded 60s poll / Desktop no-geolocation leg ' same denial keys
- no-storage.test.mjs (3 cases): GEO-03 comment-stripped grep gate over
  web/src/**/*.{ts,html}, no-SW invariant, store.ts storage-assignment probe
- theme.test.mjs (4 cases): PX-02 colorScheme + luminance fallback + setStyle
  source-introspection (Pitfall 7) + themeChanged re-apply; document stub
  installed and restored per case
- Rule 1 fix in telegram.ts: looksStale/nudgeAndRetryTelegram accept an
  omitted lastAccepted (undefined normalized to null) ' acquireFix(deps)
  first-call contract
2026-10-22 23:46:17 +08:00
araharah-executor 0a35bce256 feat(03-02): Telegram platform wrappers — theme slot switch, BackButton total wire, safe-area union
- theme.ts: colorScheme → data-tg-theme on <html>; header_bg_color 0-1 luminance fallback
  (T-03-11 — Telegram hex is a signal, never a consumed CSS value); applyTheme's ONLY
  statement is the attribute set (Pitfall 7 / D-10, greppable invariant)
- backbutton.ts: hide() at init (one shell state), onClick is total — close() at the root,
  no dead-ends (UI-SPEC §BackButton)
- safe-area.ts: env(safe-area-inset-*) probe ∪ WebApp.safeAreaInset, per-edge max,
  version-gated at 8.0, applied as --safe-* custom properties on :root
- webapp.ts + backbutton/safe-area never read node_modules types;
  @types/telegram-web-app stays dropped (web/package.json untouched)
2026-10-22 23:40:03 +08:00
araharah-executor faab77a84f feat(03-02): D-04 chain state machine + memory-only session store + /api/session client
- chain.ts: acquireFix tries telegram → webview → chat-pin in locked order; staleness
  routes to the nudge inside link 1, never to success (Pitfall 2); pluggable cadence —
  NO setInterval/watchPosition anywhere (Phase 6 picks, Open Question 2)
- isFixStale (45s = UI-SPEC signal-lost trigger) + fixAgeSeconds for FIX Ns AGO telemetry
- store.ts: module-scope token holder, zero persistence calls (the D-07 invariant);
  SESSION_TTL_SECONDS = 900 mirrors bot/src/kv/sessions.ts by name (D-03)
- client.ts: posts VERBATIM initData as the body, validates token shape, maps 401
  categories exactly (malformed/tampered/stale/network)
2026-10-22 23:39:16 +08:00
araharah-executor 9552e7e4ab feat(03-02): Fix type + three source adapters (telegram/webview/chat-pin) — GEO-01 wrappers
- types.ts: the shared Fix shape + LocationState machine (type-only, D-04/D-05/D-07)
- telegram.ts: version-gated LocationManager init/getLocation, hard 8s timeout (Pitfall 3),
  null-safe telegramLocationSupported (plain browser/Desktop = false, not TypeError),
  looksStale + haversine + the Android nudge (issue #56 navbar read DISCARDED, then retry)
- webview.ts: one-shot geolocation read, maximumAge: 0 (freshness axis), 8s timeout
- chat-pin.ts: bounded 60s poll of GET /api/location (Bearer token) + POST /api/location-fallback
  throw-on-fail so the ASK VIA CHAT handler can revalidate (D-03)
- telegram/webapp.ts created early (Rule 3: task 1 imports TelegramWebAppLike from task 3's
  file; declared the narrow hand-rolled interface now, verbatim per plan)
2026-10-22 23:37:58 +08:00
araharah-executor 3fd0694ee6 chore(03-01): lockfile resolution for the web workspace (maplibre-gl, vite, playwright-into-web) 2026-10-22 23:34:40 +08:00
araharah-executor 1fefd90b6e docs(03-01): complete web/_workspace + GEO-02 relay plan — SUMMARY 2026-10-22 23:34:33 +08:00
araharah-executor ecaf242d0e feat(03-01): GEO-02 server half both directions — chat-keyboard fallback + GET /api/location + POST /api/location-fallback (claim-scoped)
- handlers/location-fallback.ts: requestLocationFallback (ReplyKeyboardMarkup request_location:true copy from strings.ts), recordSharedPin, registerLocationFallback (filters message.location, T-03-03)
- strings.ts: awaitingPin/shareLocationButton/pinReceived in en+ms (CS1 parity) — all bot copy stays in strings.ts
- bot.ts: createBot threads env.chat_sessions into the fallback handler; optional client fetch seam for tests; one-caller rule documented
- webhook.ts: GET /api/location relay + POST /api/location-fallback trigger, both resolve chat_id & locale from getClaimByToken CLAIM, never client input (T-03-01/26/27); telegram-send failure is category-only 502 (T-03-28)
- location-pin.test.mjs: LP1–LP14 (TTL contract, range guard, cross-user isolation both routes, locale split); LP12/LP13 patch node-fetch's cached default (grammy's node shim transport), then restore
- CS4 scope untouched (handlers/ not scanned); CS1 parity automatic
2026-10-22 23:33:01 +08:00
araharah-executor 4fbbb0b516 feat(03-01): TTL-only KV location-pin layer under the existing privacy gates (GEO-03, PRIV-02)
- kv/location-pin.ts mirrors sessions.ts shape: writePin/getPin/clearPin, key loc:<chat_id> (chat-scoped, claim-derived — never user-id scoped across keys)
- PIN_TTL_SECONDS=300; writePin is the only write path, expirationTtl on the same line referencing the named constant
- getPin is a defensive read: parse guard + shape guard + |lat|<=90 / |lon|<=180 range guard (V5 Input Validation) — corrupt records return null
- privacy.test.mjs: storageModules now scans kv/location-pin.ts, so PG1 (same-line TTL) and PG2 (no persistence-shaped exports) apply automatically
2026-10-22 23:25:59 +08:00
araharah-executor 9376cabca2 feat(03-01): stand up web/ npm workspace with Wave-0 hand-rolled test runner (D-01, D-02, D-11, D-14)
- web/ workspace: TS + Vite shell (maplibre-gl 6.15.0, vite 8.3.5, TS 5.9.3; no UI framework, no Telegram SDK script tag)
- tsconfig mirrors bot/ strictness byte-for-byte except the three authorized keys (DOM lib, vite/client types, resolveJsonModule)
- test/runner.mjs + harness.mjs copied verbatim from bot/test/ (body-identical past header)
- root package.json: web workspace + web:* delegate scripts
- web/src/main.ts entry module created (tsc include:['src'] needs ≥1 input; 03-02/03-03 mount here)
2026-10-22 23:24:41 +08:00
araharah-executor af691ae40e fix(02-bot-skeleton-initdata-security): WR-03 honest rotate-on-new invariant + best-effort post-mint reconciliation (fold in IN-01b dead branch, IN-01c Phase-4 notes) 2026-10-22 20:21:11 +08:00