Extends the existing /api/v2/event beacon into a small, typed, durable event schema so the interactive views can answer product questions the request log cannot: whether a visitor ever gets a location, whether search finds anything, which controls and views earn their maintenance, and which dead ends people actually hit. Seven events with three enum dimension slots, stored as hourly aggregates in a TimescaleDB hypertable plus one JSONL line per event for the 30-day Loki view. No row per interaction and no column an identifier could go in: no cookie, no session id, no user id, no IP, no coordinates, no free text, no URLs. The IP is a per-minute rate-limit key and nothing else. Abuse resistance for a public endpoint on a 60%-crawler site: closed allowlist (unknown names collapse to one bucket), 4 KB streaming body cap, per-IP ceiling raised to 120/min (30 was sized for four coarse events and would have silently truncated a batched stream), soft Sec-Fetch-Site first-party check, uniform 204. Ships inert -- THERMOGRAPH_EVENTS gates both the recorder and the flag stamp on <html> that makes the client send anything, and is unset everywhere. See UI-EVENTS.md for the schema, the rejected transport/storage alternatives, and the privacy decisions that must be settled before the flag is turned on. |
||
|---|---|---|
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| infra | ||
| observability | ||
| CLAUDE.md | ||
| CUTOVER-NOTES.md | ||
| README.md | ||
| UI-EVENTS.md | ||
thermograph
The Thermograph monorepo — the split repos reunified (2026-07-22) with full history via subtree merges, while keeping everything the split was actually for: per-domain images, per-domain deploys, and an async FE/BE contract.
Domains
| Dir | What | CI |
|---|---|---|
backend/ |
FastAPI graded-climate API, accounts, notifications (Discord bot, push, mail), data pipeline | backend-build-push → image emi/thermograph/backend; backend-deploy[-prod|-dev] |
frontend/ |
Public client: static JS/CSS + SSR pages | frontend-* mirrors of the above; image emi/thermograph/frontend |
infra/ |
Compose, deploy scripts, terraform, SOPS secrets vault, ops cron | infra-sync (host checkout + secrets render), secrets-guard, ops-cron |
observability/ |
Loki + Grafana + Alloy stack | observability-validate |
thermograph-docs deliberately stays its own repo (ADRs + runbooks, no
build artifacts, different change cadence).
How CI stays decoupled
Every workflow in .forgejo/workflows/ is path-filtered to its domain: a
push touching only frontend/** builds/deploys nothing else. Images stay
separate (emi/thermograph/backend, emi/thermograph/frontend, each tagged
sha-<12hex>), deploys stay per-service (infra/deploy/deploy.sh SERVICE=backend|frontend|all), and the API version contract
(GET /api/version, PAYLOAD_VER) still lets FE and BE ship out of lockstep.
The one intentionally coupled piece is pr-build.yml: a single always-running
gate required check that builds only the domains a PR touches (a
path-filtered required check would deadlock auto-merge).
Branch model (unchanged from the split era): PRs → dev, main → beta,
release → prod; infra tracked via main on all hosts.
Before pointing anything live at this repo, read CUTOVER-NOTES.md.