docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
# backend/ — agent instructions
|
|
|
|
|
|
|
|
|
|
The Thermograph **API service**: a FastAPI app serving the graded-climate API
|
|
|
|
|
(`/api/v2/...`), user accounts, notifications, and the SSR-content JSON API the
|
|
|
|
|
frontend renders pages from. It owns the climate data pipeline and the
|
|
|
|
|
derived-payload cache. It serves **no HTML/CSS/JS** — that is `frontend/`.
|
|
|
|
|
|
|
|
|
|
Read the root `CLAUDE.md` first for the branch model, deploy contract and image
|
|
|
|
|
names.
|
|
|
|
|
|
|
|
|
|
## Build & verify
|
|
|
|
|
|
|
|
|
|
- `make test` — builds a py3.12 venv and runs the hermetic pytest suite
|
|
|
|
|
(`scripts/test.sh`; pass `ARGS=...`). Tests are hermetic per `tests/conftest.py`:
|
|
|
|
|
no real Open-Meteo/Nominatim/GeoNames calls, throwaway SQLite, notifier thread
|
|
|
|
|
disabled.
|
|
|
|
|
- `make smoke` — boots the built image plus a throwaway db and asserts
|
|
|
|
|
`/healthz` + `/api/version`.
|
|
|
|
|
- CI runs this suite **inside the built image** (`build.yml`), so the exact
|
|
|
|
|
interpreter and deps that ship are what gets tested.
|
|
|
|
|
|
|
|
|
|
`app.py` at the root is a one-line re-export shim for `web/app.py`, kept stable
|
|
|
|
|
for systemd/CI callers. Entry target is `app:app`.
|
|
|
|
|
|
|
|
|
|
## Contracts that break silently if you change them
|
|
|
|
|
|
|
|
|
|
- **`GET /api/version`** → `{backend_version, min_frontend, payload_ver}`, driven
|
|
|
|
|
by `API_CONTRACT_VERSION` and `MIN_SUPPORTED_FRONTEND` in `web/app.py`. A
|
|
|
|
|
breaking change ships as a new `v3` router mounted **alongside** `v2`,
|
|
|
|
|
re-registering only changed handlers, with the constant bumped in the same PR.
|
|
|
|
|
`/api` and `/api/v1` stay mounted as aliases.
|
|
|
|
|
- **`PAYLOAD_VER`** (`api/payloads.py`) is separate from URL versioning — it is
|
|
|
|
|
the cache/ETag invalidation token. Bump it whenever a response payload's shape
|
|
|
|
|
changes, so one bump atomically orphans every pre-upgrade cached row.
|
|
|
|
|
- **ETag / `If-None-Match`** — every graded payload is cached against a validity
|
|
|
|
|
token that doubles as a weak ETag. `expose_headers=["ETag"]` in the CORS
|
|
|
|
|
middleware matters as much as `allow_origins`: without it the frontend's
|
|
|
|
|
`cache.js` reads `null` cross-origin and never revalidates. Don't change the
|
|
|
|
|
ETag derivation or that list without checking `frontend/static/cache.js`.
|
|
|
|
|
- **`data/grading.py::pct_ordinal()`** is mirrored by `frontend`'s
|
|
|
|
|
`shared.js::pctOrd()` — floor a percentile into `1..99`, never 0 or 100.
|
|
|
|
|
`TEMP_BANDS`/`RAIN_BANDS` are the source of truth for tier names and
|
|
|
|
|
thresholds; changing them without the frontend produces tiers drawn in colours
|
|
|
|
|
that disagree with the labels the API returns.
|
|
|
|
|
- **Fahrenheit country set** — `api/content_payloads.py`'s `F_COUNTRIES` must stay
|
|
|
|
|
identical to `frontend`'s `format.py::F_COUNTRIES` and `static/units.js`'s
|
|
|
|
|
`F_REGIONS`. There is a test asserting this.
|
|
|
|
|
- **`/healthz` and `/api/version` are deliberately I/O-free** so they stay cheap
|
|
|
|
|
under tight healthcheck intervals.
|
2026-07-22 18:58:36 +00:00
|
|
|
|
docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
## Layout
|
2026-07-22 18:58:36 +00:00
|
|
|
|
docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
`accounts/` (fastapi-users + alembic), `api/` (payload builders + SSR content
|
|
|
|
|
routes), `core/` (metrics, audit, singleton helper), `data/` (grid, climate
|
|
|
|
|
fetch/cache, grading/scoring, places/cities, derived-payload store),
|
|
|
|
|
`notifications/` (push, email, Discord bot, digest, scheduler), `web/app.py`
|
|
|
|
|
(the real app), `daemon/`.
|
2026-07-22 18:58:36 +00:00
|
|
|
|
docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
`paths.py` resolves every filesystem location from the repo root — don't
|
|
|
|
|
reintroduce `__file__`-relative paths in a module; it breaks the moment a module
|
|
|
|
|
moves. `deploy/entrypoint.sh` is the container entrypoint (alembic migrate, then
|
|
|
|
|
serve, with a Swarm-secrets-as-files shim).
|
2026-07-22 18:58:36 +00:00
|
|
|
|
docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
## Commits & PRs
|
2026-07-22 18:58:36 +00:00
|
|
|
|
docs: rewrite the agent context layer to match the live system
The four domain CLAUDE.md files still described the pre-monorepo split-repo
topology, and several statements were the exact inverse of current reality:
infra/CLAUDE.md told a reader that emi/thermograph is archived and must not be
pointed at, when that is the live repo; backend/ and frontend/ both claimed
there is no Makefile and no requirements-dev.txt when all four files exist;
frontend/ described itself as one of four sibling repos; observability/ claimed
a single unprotected main branch.
These files are read before every change, so a stale one is a correctness
problem rather than a documentation one. Rewritten against the tree:
- Root CLAUDE.md now owns the cross-cutting truth once — branch model, the
SERVICE + *_IMAGE_TAG deploy contract, image names, which orchestrator each
environment runs, and that everything is a PR. Domain files carry only what
differs and are capped at ~70 lines.
- Records that prod runs Swarm from deploy/stack/thermograph-stack.yml while
beta and LAN dev run compose, routed by /etc/thermograph/deploy-mode.
- Documents the *-deploy-dev.yml workflows as inert rather than leaving a
reader to discover it.
Deletes infra/docker-stack.yml. It defined db/app/worker, matched nothing that
deploys, and was referenced only by prose describing it as a future design
record — while the real 8-service prod stack lives under deploy/stack/. A file
that looks authoritative and affects nothing is the worst case for a reader
asked to change the prod stack.
infra/README.md claimed "compose in production today" and that Swarm was "not
currently live"; corrected, and infra-sync.yml's existence is now recorded
instead of "there is no separate infra deploy trigger". The compose file's
timescale-pin comment now points at how deploy-stack.sh actually resolves the
digest from the running container.
2026-07-25 03:59:44 +00:00
|
|
|
Concise and technical. Never mention AI, assistants or automated authorship.
|