docs: full docs-update pass — 9 GSD docs regenerated + verified (434→454 claims; verifier corrections applied)

This commit is contained in:
araharah-executor
2026-10-25 12:51:01 +08:00
parent 809864f5b6
commit 44851c70fe
9 changed files with 2014 additions and 324 deletions
+248
View File
@@ -0,0 +1,248 @@
<!-- generated-by: gsd-doc-writer -->
# Contributing to arah²
arah² (the `araharah` repository) is a privacy-respecting, Telegram-first GPS navigator
for the Kuala Lumpur metro — a grammY bot + Cloudflare Worker, a Vite/MapLibre Mini App,
a design-token package, a curated KL gazetteer, and the routing engine behind the
turn-by-turn guidance. Code is **GPL-3.0**; the map-derived data files are **ODbL-1.0**.
This file tells you what belongs in the project, how to set up, and — most importantly —
which gates a change must pass before a maintainer will look at it. The repo has no CI,
so *you* are the CI.
---
## The charter — what belongs here, and what never will
arah²'s core value: **a user finds a local destination via Telegram and gets there with
reliable turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in
to.** The project charter (`.planning/PROJECT.md`, tracked in this repo) records the
scope boundaries and, crucially, *why* each exclusion was made, so they don't get
re-litigated every milestone.
**In scope (v1):** the Telegram bot command surface, destination search over the curated
KL gazetteer, the Mini App webview, route display, live turn-by-turn guidance with
hybrid chat turn-cards, KL-metro tuning, and the Telemetry Rams design system.
**Out of scope — the deferred-ideas law** (from `.planning/PROJECT.md` "Out of Scope"):
- **Incident reports** (`/accident`, `/traffic`, `/hazard` + broadcasts) — navigation
first; reports ship after the core works. v1 reports *nothing*.
- **Gamification, points, leaderboards** — depends on reports; deferred with them.
- **Regional clusters via Telegram groups** — unvalidated social hypothesis.
- **Voice-to-text reporting, offline mode, anonymized public API** — future ideas.
- **Push notifications beyond Telegram chat** (Resend email etc.) — Telegram itself is
the channel.
- **Independent mobile apps** — the Mini App webview flow *is* the product.
- **Any paid-tier infrastructure.** The $0 law is a product constraint, not an
aspiration — see [docs/BUDGETS.md](docs/BUDGETS.md). IDEAS.md's RabbitMQ,
Prometheus/Grafana, Neon, and Resend entries are aspirational; do not wire them in.
If your proposal touches any of these, the bar is high: it needs a new charter decision,
not just a PR. Architecture seams for the deferred work exist deliberately (e.g. the
`SearchSource` interface), but the seams stay unused.
---
## Development setup
Read [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) for prerequisites and first-run
instructions, and [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full local
development setup. The short version:
1. Clone and install — npm workspaces install all five packages at once:
```bash
git clone http://gitmirah:3000/c-fu/arah2.git
cd arah2
npm install
```
<!-- VERIFY: the clone URL is the maintainer's internal Gitea origin (per `git remote -v` and README.md), not a public URL — forks/substitutes should clone their own fork's URL. -->
2. Copy `bot/.dev.vars.example` to `bot/.dev.vars` and fill in `BOT_TOKEN`,
`WEBHOOK_SECRET`, and `WEBHOOK_URL` (gitignored; never commit it).
3. `npm run build` — theme tokens first, then the web bundle (the theme build is an
input to the web build).
4. `npm run dev` — Worker on <http://localhost:8787>.
There is no linter, formatter, or commit-hook setup to install — see
[Code standards](#code-standards) below.
---
## Code standards
- **No ESLint, Prettier, Biome, or `.editorconfig` exists.** There is no `lint` or
`format` script in any workspace and no CI to enforce one. Style is a review-time
convention; the mechanically enforced gates are listed in the next section. Match the
existing source: explicit `.ts` extensions on relative imports, `node:`-prefixed
builtins, `.mjs` for scripts/tests, heavy header comments stating what a file is and
which decision IDs it satisfies.
- **TypeScript strictness is real:** both `bot/tsconfig.json` and `web/tsconfig.json`
use `strict` + `noUncheckedIndexedAccess` + `verbatimModuleSyntax`; `search/` and
`routing/` follow the same shape. `tsc --noEmit` (`npm run *:typecheck`) is the gate.
- **Design tokens:** never hardcode a color, spacing, font, or radius that belongs to a
token. `theme/src/tokens.css` is the single source of truth; `npm run audit` flags
off-token values in rendered CSS. Never edit `stitch_minimalist_route_navigator/` —
the mockups are read-only reference.
- **All user-facing copy lives in the i18n strings files** (`bot/src/i18n/strings.ts`,
`web/src/i18n/strings.ts`) with `en`/`ms` locale parity (gate `COPY1`). No inline copy
in shell modules (gate `COPY5`).
---
## The gates — what must be green before review
There is **no CI pipeline** (no `.github/`, no `.gitea/` workflows). Run these by hand
before opening a change, and paste the output into your PR:
```bash
npm run build # FIRST — web:test's BA cases read web/dist
npm run bot:test # bot/test/runner.mjs
npm run web:test # web/test/runner.mjs
npm run search:test # search/test/runner.mjs
npm run routing:test # routing/test/runner.mjs
npm test -w theme # theme/scripts/audit.test.mjs (no root passthrough)
npm run bot:typecheck
npm run web:typecheck
npm run search:typecheck
npm run routing:typecheck
npm run audit # off-token CSS audit — only if you touched theme/ or rendered CSS
```
Order matters in one place: **`npm run build` before `npm run web:test`**, or the
build-artifact gates (BA1–BA10) fail with a "build first" message.
### The repo's law enforcement: the two audit CLIs
Two maintainer-time tools (nothing in any `src` tree imports them) enforce the project's
non-negotiable laws. **A PR that makes either one fail is a regression, full stop.**
- **`npm run privacy-audit`** ([docs/PRIVACY-AUDIT.md](docs/PRIVACY-AUDIT.md),
`scripts/privacy-audit.mjs` + `scripts/lib/`) — the privacy law:
- **TTL-only storage:** every KV `.put(` must carry `expirationTtl` on the same line,
referencing a named `SCREAMING_SNAKE` constant (the `kv-ttl` gate; PG1 is the
suite-level twin). The four KV families are the complete storage surface.
- **Category-only logs:** no log line may carry a coordinate, chat id, user id,
token, initData payload, geometry, or serialized payload (the `log-vocabulary`
gate).
- **Zero telemetry:** no analytics, beacons, tracking pixels, third-party CDN, or
error-reporting host — in source *and* in the shipped `web/dist` bundle
(`storage-api`, `runtime-hosts`, `bundle-telemetry` gates). v1 has nothing to opt
into.
- **`npm run budget-audit`** ([docs/BUDGETS.md](docs/BUDGETS.md),
`scripts/budget-audit.mjs` + `scripts/lib/budget-gates.mjs`) — the $0 law: bundle
sizes, KV write classes (the cron tick writes **zero** — source-proven), subrequest
inventories, and the golden corpora, all re-derived against the Cloudflare free-plan
ceilings. If you add a route class or a KV write path, its inventory row must follow
the code.
Both audits are GPL-3.0 and reusable by forks; rerun them over your own tree before your
first driver. The `--live` legs (KV expiry, cache headers) need Cloudflare credentials
and are maintainer-time.
### The capture harness
For any Mini App UI change, `npm run web:capture` (Playwright, against the harness build
in `web/dist-harness/`) is the verification surface: screenshot legs over every
boot/route/guidance state, a DSGN-02 side-by-side against the committed mockup, and the
GEO-03 runtime storage probe that fails if any app-controlled storage surface is
non-empty after boot-to-live. Include the relevant captures in your PR. Outputs are
gitignored; regenerate, never commit.
---
## Branch and commit conventions
- The active branch is **`main`**. Work lands there via atomic commits; if you prefer a
branch, `feat/`, `fix/`, and `docs/` prefixes match the commit vocabulary. No
branch-protection or required-reviewer config exists.
- Commit messages are **conventional-commit style with a phase-scoped type**:
`type(phase): subject` — types `feat`, `fix`, `docs`, `chore`, `test`; the scope is a
phase identifier (e.g. `07-06`). Examples from `git log`:
```
fix(07-06): launch-gate decisions — KV cron retune to 720/day (the $0 law holds)
test(07-06): RED — SA6 doc-pointer static-text class vector (URL literals only)
```
Subject is lowercase, imperative, and carries an em-dash clause explaining the *why*
or the observable failure. Reference decision IDs (`D-09`, `PRIV-02`, `GEO-03`) in
parentheses when relevant. One concern per commit.
---
## Pull requests
There is no PR template and no configured review process (this is a private Gitea
repo — `git remote -v` points at `http://gitmirah:3000/c-fu/arah2.git`). If you open a
PR there, reviewers look for:
- **All gates green, pasted.** The full list from [the gates](#the-gates--what-must-be-green-before-review)
above, plus `npm run privacy-audit` and `npm run budget-audit` if you touched any
storage, logging, network, bundle, or KV surface — i.e. if in doubt, run both audits.
- **The observable before/after**, stated like a commit subject: what was broken, what
it does now.
- **Gate-relevant surface called out explicitly.** Touched a KV write? Say which
`expirationTtl` and which gate re-verified it. Touched `web/src`? `GEO3`/`COPY1`/`COPY5`
re-run. Touched the command surface? `CS4`/`CS6` re-run.
- **Screenshots** for Mini App changes (from `npm run web:build:harness &&
npm run web:capture`).
- **No generated artifacts committed.** `theme/dist`, `web/dist`, `web/dist-harness`,
and all `screenshots/` directories are gitignored by design — always rebuild. In
particular, committed `theme/dist` would go stale because the web build consumes it
at build time without vendoring.
---
## Licensing your contribution
- **Code is GPL-3.0.** The full text is in [LICENSE](./LICENSE), and GPL-3.0 is a
deliberate project law (decision D-04, per [LICENSES.md](./LICENSES.md)): a fork must
be able to read, change, and redistribute the whole working system. There is **no CLA
and no DCO configured** — no `Signed-off-by` lines exist in the commit history, and no
sign-off mechanism is set up. By submitting a PR you agree your code contribution is
licensed under GPL-3.0 alongside the rest of the project. If you add a new source
file, a copyright header is welcome but not required.
- **Map-derived data is ODbL-1.0, not GPL.** The curated gazetteer
(`search/src/data/kl-gazetteer.json`), the route fixtures
(`routing/src/data/route-fixtures.json`), and the derived road graph are all curated
or derived from OpenStreetMap data — **© OpenStreetMap contributors, ODbL 1.0**
(<https://opendatacommons.org/licenses/odbl/1-0/>). The attribution clause travels
with derived databases, so:
- Never remove the `_license` / `_attribution` / `_notice` envelope fields from a
data file.
- Any new data file derived from OSM must carry the same envelope.
- Any restructured/derived index of the gazetteer carries the same notice (ODbL §4.4
share-alike for Derivative Databases). Code and data are separately licensed — ODbL
§2.3(a) excludes computer programs.
- See [docs/ROAD-GRAPH.md](docs/ROAD-GRAPH.md) for the road-graph licensing surface.
---
## Reporting issues and proposing ideas
There are no issue templates (no `.github/ISSUE_TEMPLATE/`). The repository lives on a
private Gitea instance at `http://gitmirah:3000/c-fu/arah2` — use its issue tracker if
you have access. <!-- VERIFY: Gitea issue-tracker availability and URL path — the remote is confirmed via `git remote -v`, but the tracker is on the maintainer's internal instance and cannot be verified from the repository contents. -->
For a bug report, include:
- Steps to reproduce, expected vs actual behavior, and environment (which Telegram
client surface — `?mock=` value if you used the harness — and OS).
- The failing gate output, verbatim: the runners and audits print evidence strings that
are meant to be quoted, never paraphrased.
For a feature proposal, first check the charter above: if it's in the deferred list,
argue why the deferral no longer holds, and expect that discussion to happen at the
charter level (`.planning/PROJECT.md`), not in the PR.
**Where decisions live.** Project-level decisions are recorded in `.planning/PROJECT.md`
(charter, key decisions), `.planning/ROADMAP.md`, and per-phase documents under
`.planning/phases/` — all tracked in the repo. The checked-in evidence records are the
`docs/` files: [PRIVACY-AUDIT.md](docs/PRIVACY-AUDIT.md) (the signed privacy audit),
[BUDGETS.md](docs/BUDGETS.md) (the measured budget record),
[SEARCH-QUALITY.md](docs/SEARCH-QUALITY.md) and [ROAD-GRAPH.md](docs/ROAD-GRAPH.md)
(validation records), and [KNOWN-LIMITATIONS.md](docs/KNOWN-LIMITATIONS.md). If a
reviewer asks "where was this decided?", the answer is one of those files.