1. CLAUDE.md and README.md described the superseded Python service. Both now describe server/ (Go), say plainly that the Python files at that level are the original the port was made from, and drop CLAUDE.md's claim that `make test-unit` is "the tier CI runs" — CI's only frontend check is the Dockerfile builder stage's gofmt + vet + go test. 2. static/units.js's F_REGIONS was guarded by nothing, despite three source comments claiming "a test asserts all three stay identical": the only check compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS now diffs the browser copy both directions. That backend cross-check also skips in CI — the image build context is frontend/, so backend/ is unreachable from the builder stage, which is the only place CI runs these tests. static/ IS in the context, so the Dockerfile copies units.js into the builder and the new assertion runs during the image build. Verified by mutating units.js and confirming the build fails. 3. Both docker-compose.test.yml files defaulted to the retired emi/thermograph-backend/app path, and the frontend harness pinned the split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one hardcoded pin for another, backend-for-tests.sh now derives the tag from the checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed rule build-push.yml and deploy.yml use — and compose requires the variable so a stale pin cannot creep back in. Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages ok; frontend image builds; `make backend-up` pulls and serves on the derived tag; shellcheck zero findings across the tree. Unrelated pre-existing issue noted in the docs, not fixed here: `make test-integration` fails 7/16 with 503 against a cold throwaway backend (empty database, nothing warm). Reproduced identically on the old image, so it predates this change. Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ |
||
|---|---|---|
| .. | ||
| 01-orientation.md | ||
| 02-setup.md | ||
| 03-repo-map.md | ||
| 04-backend.md | ||
| 05-frontend.md | ||
| 06-contracts.md | ||
| 07-ci-and-release.md | ||
| 08-infra-secrets.md | ||
| 09-observability.md | ||
| 10-recipes.md | ||
| 11-traps.md | ||
| README.md | ||
Onboarding — becoming a full contributor to Thermograph
This is the developer onboarding path for the emi/thermograph monorepo: what
the product is, how the code is shaped, how to run it, what will break silently
if you get it wrong, and how a change actually travels from your editor to
thermograph.org.
Scope. This covers working in this repo. Cross-cutting architecture
decision records and operator runbooks live in the separate thermograph-docs
repo (reachable through Centralis's docs_search) — this set links out rather
than duplicating them. Where a fact is owned by a CLAUDE.md, that file stays
the source of truth and this set explains the context around it.
Reading order
| # | Doc | Read it when |
|---|---|---|
| 1 | Orientation | First. What the product actually claims, the estate, the four non-negotiable rules. |
| 2 | Local setup | Before you touch anything. Toolchain, verified run/test recipes for every service. |
| 3 | Repo map | To find things. Every domain and directory, and which files matter. |
| 4 | Backend deep dive | Before your first backend change. Data pipeline, grading, caching, roles, daemon. |
| 5 | Frontend deep dive | Before your first frontend change. The Go SSR service, static assets, design system. |
| 6 | Cross-service contracts | Before any change that touches both. These break silently and in production. |
| 7 | CI and release | Before you open a PR. Nine workflows, three branches, three environments. |
| 8 | Infra and secrets | Before you touch deploy, compose, or a secret. |
| 9 | Observability | When something is wrong and you need to see it. |
| 10 | Recipes | Task-shaped walkthroughs for the things you'll actually do. |
| 11 | Traps and stale docs | Skim early, re-read often. Which docs in this repo currently lie, and why. |
The short version
Thermograph grades how unusual today's weather is at any point on Earth against ~45 years of that exact location's own history. Percentiles, never thermometer readings.
backend/— Python 3.12 / FastAPI. Owns all climate data, grading, the API, accounts, notifications, plus a Go daemon (backend/daemon/) that owns the Discord gateway and recurring timers. Ships asemi/thermograph/backend.frontend/— Go SSR service (frontend/server/) plus every static asset. No climate data, no database, no compute. Ships asemi/thermograph/frontend. (The Python implementation atfrontend/*.pyis the superseded original — see traps.)infra/— compose and Swarm files, deploy scripts, the SOPS secrets vault, Terraform, ops query tooling.observability/— Loki + Grafana on beta, an Alloy agent per node.
Branches stage environments: PR → dev (LAN dev) → main (beta) → release
(prod). The two app domains build and deploy independently — that
independence is the whole reason the split-then-reunify history exists, and
contracts is the list of things that keep it safe.
Your first day
- Read Orientation and Traps.
- Work through Local setup until
make testis green in bothbackend/andfrontend/server/. - Read Repo map with the tree open beside it.
- Pick something small in the domain you're least afraid of, and follow Recipes end to end — including opening the PR.
Your first week
- Read Contracts properly. Every item on that list has already cost someone a production incident somewhere in this project's history; that's why each one is written down.
- Get Centralis wired up (see Local setup) and ask it
fleet_status,logs_overview,deployed_version. You cannot debug this estate blind, and the boxes are not reachable from a laptop off the WireGuard mesh. - Read the
CLAUDE.mdfor each domain. They are terse, current, and binding.