Files
arah2/CONTRIBUTING.md

249 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 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.