Commit graph

503 commits

Author SHA1 Message Date
emi
d6ead56971 bookmarks: map + account UI for saved locations
All checks were successful
secrets-guard / encrypted (pull_request) Successful in 10s
PR build (required check) / changes (pull_request) Successful in 16s
shell-lint / shellcheck (pull_request) Successful in 12s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 2m24s
PR build (required check) / build-backend (pull_request) Successful in 2m51s
PR build (required check) / gate (pull_request) Successful in 3s
2026-07-26 18:36:34 +00:00
emi
4e1c4a4197 bookmarks: map + account UI for saved locations 2026-07-26 18:36:31 +00:00
emi
41858f3e4b bookmarks: account-synced saved locations API
All checks were successful
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-backend (pull_request) Successful in 1m0s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-26 18:27:45 +00:00
emi
58e3a8d625 bookmarks: account-synced saved locations API 2026-07-26 18:27:44 +00:00
emi
53fd5d9225 bookmarks: account-synced saved locations API 2026-07-26 18:27:43 +00:00
emi
3fafd67600 bookmarks: account-synced saved locations API 2026-07-26 18:27:41 +00:00
emi
67284dfe2e bookmarks: account-synced saved locations API 2026-07-26 18:27:40 +00:00
emi
8572c4aa6d accounts: sign in with Discord, sharing the existing link callback (#115)
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 11s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 7s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 1m21s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 1m19s
Deploy / deploy (frontend) (push) Successful in 1m27s
Deploy / deploy (backend) (push) Successful in 1m49s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / changes (pull_request) Successful in 7s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m16s
PR build (required check) / build-backend (pull_request) Successful in 1m36s
PR build (required check) / gate (pull_request) Successful in 1s
2026-07-26 18:16:55 +00:00
emi
801c767115 dev: leave /healthz outside the basic-auth boundary (#113)
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
shell-lint / shellcheck (push) Successful in 10s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 6s
PR build (required check) / changes (pull_request) Successful in 5s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
2026-07-26 08:28:18 +00:00
emi
4122f4353b dev: serve at dev.thermograph.org behind basic auth, bound to loopback (#111)
Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 5s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 16s
PR build (required check) / changes (pull_request) Successful in 14s
secrets-guard / encrypted (pull_request) Successful in 13s
shell-lint / shellcheck (pull_request) Successful in 11s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
2026-07-26 07:49:43 +00:00
emi
64986189e1 deploy.sh: honour TG_POST_DEPLOY on the compose path too (#109)
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 7s
PR build (required check) / changes (pull_request) Successful in 7s
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 9s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
Sync infra to hosts / sync-prod (push) Has been skipped
secrets-guard / encrypted (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
2026-07-26 07:28:43 +00:00
emi
5c41ee4dad provision-env-db: revoke PUBLIC connect on the owner database too (#108)
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 7s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 7s
2026-07-26 07:17:20 +00:00
emi
7583258509 infra-sync: refuse to render dev's vault onto a host that is not dev (#107)
Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 5s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 8s
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 7s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 11s
PR build (required check) / validate-observability (pull_request) Successful in 21s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-26 07:05:09 +00:00
emi
d138f00a20 infra: split the estate into vps1/vps2 — beta joins prod, dev gets a home (#103)
Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 18s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-26 06:56:38 +00:00
emi
5b7e30dad9 fix(frontend): Weekly view must always send an explicit date to /grade
All checks were successful
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 9s
secrets-guard / encrypted (pull_request) Successful in 17s
shell-lint / shellcheck (pull_request) Successful in 16s
PR build (required check) / changes (pull_request) Successful in 20s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 59s
PR build (required check) / build-frontend (pull_request) Successful in 35s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-26 06:20:19 +00:00
emi
5f20fba9f5 frontend: fix the three inconsistencies the onboarding guide found (#99)
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 18s
PR build (required check) / changes (pull_request) Successful in 22s
shell-lint / shellcheck (pull_request) Successful in 19s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 1m14s
PR build (required check) / build-backend (pull_request) Successful in 1m21s
PR build (required check) / gate (pull_request) Successful in 2s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 1m11s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m1s
2026-07-25 21:11:32 +00:00
Emi Griffith
12441be0c1 docs: add a developer onboarding guide for the monorepo
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 6s
Twelve documents under docs/onboarding/ covering orientation, local setup,
the repo map, per-domain deep dives, the cross-service contracts, CI and the
release flow, infra and secrets, observability, task recipes, and a list of
which docs in this tree are currently stale.

Every command in the setup guide was run against this checkout: the backend
suite (429 passed, 8 skipped), the frontend Go suite, a venv boot of the
backend, and a build+boot of the Go frontend.

Two findings recorded along the way:

- static/units.js's F_REGIONS is guarded by no test, despite three source
  comments claiming "a test asserts all three stay identical". The Go test
  only cross-checks the Go copy against the backend's Python. All four copies
  are currently identical.
- backend/ and frontend/docker-compose.test.yml still default to the retired
  emi/thermograph-backend/app image path, and the frontend harness pins the
  split-era v0.0.2-split-ci tag.

Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:34:44 +00:00
emi
b32b5841f3 alerting: don't print a bare "value:" when ValueString is empty (#96)
All checks were successful
secrets-guard / encrypted (push) Successful in 14s
Validate observability stack / validate (push) Successful in 17s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (push) Successful in 14s
PR build (required check) / validate-observability (pull_request) Successful in 20s
PR build (required check) / changes (pull_request) Successful in 8s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-25 18:17:37 +00:00
emi
1a56a63800 alerting: base web/worker liveness on the app heartbeat, not container stdout (#94)
All checks were successful
shell-lint / shellcheck (push) Successful in 7s
secrets-guard / encrypted (push) Successful in 5s
Validate observability stack / validate (push) Successful in 12s
secrets-guard / encrypted (pull_request) Successful in 7s
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 18s
PR build (required check) / changes (pull_request) Successful in 8s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-25 17:51:42 +00:00
emi
d4a00099f2 climate: stop dropping the current day from the Open-Meteo bundle (#92)
All checks were successful
secrets-guard / encrypted (push) Successful in 10s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 9s
secrets-guard / encrypted (pull_request) Successful in 13s
shell-lint / shellcheck (pull_request) Successful in 15s
PR build (required check) / changes (pull_request) Successful in 19s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 1m13s
PR build (required check) / build-backend (pull_request) Successful in 1m6s
PR build (required check) / gate (pull_request) Successful in 1s
2026-07-25 16:56:21 +00:00
emi
52cfe5b77a Merge pull request 'Merge main back into dev to reconcile the squash-promotion divergence' (#91) from sync/main-into-dev into dev
All checks were successful
secrets-guard / encrypted (push) Successful in 6s
PR build (required check) / changes (pull_request) Successful in 11s
shell-lint / shellcheck (push) Successful in 13s
secrets-guard / encrypted (pull_request) Successful in 9s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 11s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
2026-07-25 08:45:51 +00:00
Emi Griffith
be4aa940d9 Merge main back into dev to reconcile the squash-promotion divergence
All checks were successful
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / changes (pull_request) Successful in 11s
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
The dev → main promotion could not merge: `main` sat 22 commits ahead of `dev`
with content dev had never seen, and git found genuine conflicts in CLAUDE.md
and README.md plus modify/delete on the eight workflow files #87 removed.

The cause is that recent promotions were merged with squash rather than "Create
merge commit". A squash replays dev's commits onto main under new SHAs, so the
two branches share content but not history — main appears to have independently
edited the same files, and every subsequent promotion inherits the divergence.
`promote` warns about exactly this.

Resolution: both conflicts take dev's side, not as a default but because main's
side describes workflows that no longer exist (`*-deploy-dev.yml`,
`backend-build-push` and friends). Keeping it would reintroduce the class of
staleness the root CLAUDE.md now forbids — statements naming files that are not
there. The eight deleted workflows stay deleted; the merge result carries the
nine that #87 left behind.

Verified after resolution: no conflict markers anywhere, no unmerged paths, no
document naming a deleted workflow, all nine workflow files parse, and
shellcheck is clean across the tree.

Promotions from here should use "Create merge commit" so this does not recur.
2026-07-25 01:44:54 -07:00
emi
d42a57a011 ci: collapse the eight deploy and build-push workflows into two (#87)
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 6s
PR build (required check) / changes (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 9s
PR build (required check) / gate (pull_request) Successful in 2s
secrets-guard / encrypted (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
2026-07-25 07:48:49 +00:00
emi
dde2cdd6e2 infra: mirror LAN dev secrets under $HOME for snap-confined Docker (#85)
Some checks failed
Sync infra to hosts / sync-beta (push) Failing after 13s
Sync infra to hosts / sync-prod (push) Successful in 25s
secrets-guard / encrypted (push) Successful in 22s
Validate observability stack / validate (push) Successful in 27s
shell-lint / shellcheck (push) Successful in 21s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m24s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 1m21s
Deploy backend to beta VPS / deploy (push) Successful in 2m4s
Deploy frontend to beta VPS / deploy (push) Successful in 2m12s
2026-07-25 07:19:18 +00:00
emi
7307a7aadc guardrails: enforce live-host and secrets policy with hooks (#82) 2026-07-25 07:19:18 +00:00
emi
1acf8414ff docs: rewrite the agent context layer to match the live system (#81) 2026-07-25 07:19:18 +00:00
emi
bee5b21587 infra/forgejo: give the LAN runner's PATH ~/.local/bin (#84) 2026-07-25 07:19:18 +00:00
emi
9b6b12a5da infra: mirror LAN dev secrets under $HOME for snap-confined Docker (#85)
All checks were successful
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 7s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Successful in 38s
PR build (required check) / build-frontend (pull_request) Successful in 1m6s
PR build (required check) / build-backend (pull_request) Successful in 1m25s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-25 07:16:08 +00:00
emi
fbdfebf873 guardrails: enforce live-host and secrets policy with hooks (#82)
All checks were successful
shell-lint / shellcheck (push) Successful in 14s
secrets-guard / encrypted (push) Successful in 15s
PR build (required check) / changes (pull_request) Successful in 18s
secrets-guard / encrypted (pull_request) Successful in 15s
shell-lint / shellcheck (pull_request) Successful in 18s
PR build (required check) / validate-observability (pull_request) Successful in 37s
PR build (required check) / build-frontend (pull_request) Successful in 3m1s
PR build (required check) / build-backend (pull_request) Successful in 3m32s
PR build (required check) / gate (pull_request) Successful in 4s
2026-07-25 07:09:11 +00:00
emi
e84b1f7937 docs: rewrite the agent context layer to match the live system (#81)
Some checks failed
shell-lint / shellcheck (push) Has been cancelled
secrets-guard / encrypted (push) Has been cancelled
Validate observability stack / validate (push) Successful in 33s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 3m5s
Deploy frontend to LAN dev server / build (push) Successful in 3m21s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 3m27s
Deploy backend to LAN dev server / build (push) Successful in 4m16s
Deploy frontend to LAN dev server / deploy (push) Successful in 31s
Deploy backend to LAN dev server / deploy (push) Successful in 1m19s
2026-07-25 07:08:54 +00:00
emi
2c7ceefab4 infra/forgejo: give the LAN runner's PATH ~/.local/bin (#84)
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 6s
2026-07-25 06:59:21 +00:00
emi
be88987d95 Merge pull request 'promote: dev → main' (#83) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 7s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 44s
Deploy backend to beta VPS / deploy (push) Successful in 48s
2026-07-25 06:06:09 +00:00
emi
a4ecb51401 web/worker: add a process-level liveness heartbeat (#80)
Some checks failed
secrets-guard / encrypted (push) Successful in 24s
shell-lint / shellcheck (push) Successful in 26s
Deploy frontend to LAN dev server / build (push) Successful in 2m11s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 2m20s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 2m28s
Deploy backend to LAN dev server / build (push) Successful in 2m45s
PR build (required check) / changes (pull_request) Successful in 16s
secrets-guard / encrypted (pull_request) Successful in 16s
shell-lint / shellcheck (pull_request) Successful in 15s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
Deploy frontend to LAN dev server / deploy (push) Successful in 39s
PR build (required check) / build-backend (pull_request) Successful in 1m21s
PR build (required check) / gate (pull_request) Successful in 3s
Deploy backend to LAN dev server / deploy (push) Failing after 2m31s
2026-07-25 04:13:47 +00:00
emi
6c6842ab9e Merge pull request 'Promote dev to main: dev + Centralis vaults' (#78) from dev into main
All checks were successful
Sync infra to hosts / sync-prod (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 7s
shell-lint / shellcheck (push) Successful in 9s
Sync infra to hosts / sync-beta (push) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 8s
2026-07-25 01:16:04 +00:00
emi
72ad6f300f Merge pull request 'secrets: vault dev and Centralis; render dev without common.yaml' (#77) from feat/centralis-vault into dev
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-backend (pull_request) Successful in 1m8s
PR build (required check) / gate (pull_request) Successful in 2s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Successful in 54s
2026-07-25 01:14:05 +00:00
Emi Griffith
33cd69e44e secrets: silence two SC2016s that are the intended behaviour
All checks were successful
PR build (required check) / changes (pull_request) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
shell-lint failed on two single-quoted strings. Both are deliberate and
double-quoting either one would break it silently rather than loudly.

In render_centralis_secrets, the verification step runs `bash -c 'set -a; . "$1"'`
with the rendered file passed as an argument. The "$1" must be expanded by that
inner shell. Double-quoted, it would interpolate the OUTER $1 — the function's
own first argument, the repo root — so the check would source the wrong path and
still report success. That check exists precisely to catch a bad render, so a
false pass there is worse than no check.

In verify-centralis-render.sh, the string is executed by the REMOTE shell over
ssh. Double-quoted, mktemp would run locally and the script would send the host
a path that does not exist there.

Suppressed with the reason at each site rather than loosened globally.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
2026-07-24 18:13:08 -07:00
Emi Griffith
43c9e2052a secrets: vault dev and Centralis; render dev without common.yaml
Some checks failed
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Failing after 9s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
Brings the last two credential stores under SOPS, so all three environments and
the control plane are managed the same way.

dev.yaml — dev was not "intentionally not wired in", it was broken

The vault README claimed dev had no real secrets. Both halves were false.

Dev needed secrets it did not have: thermograph-dev-daemon-1 has been in a crash
loop, restarting every ~60s with "neither THERMOGRAPH_INTERNAL_TOKEN nor
THERMOGRAPH_AUTH_SECRET is set; refusing to start" — so dev has had no Discord
gateway and no job timers. With THERMOGRAPH_BASE_URL unset the app also falls
back to https://thermograph.org, so dev's IndexNow pings and verification links
claimed to be prod. dev.yaml gives it its own generated AUTH_SECRET and a real
base URL, and 12 values total.

And dev already held production credentials: backend-deploy-dev.yml injects
S3_ACCESS_KEY/S3_SECRET_KEY into every dev deploy, and the only provisioned
keypair for that bucket is read-write — on the bucket holding prod's backups.
Dev does not need them; without bucket creds the lake service 503s and history
falls through to the archive. Those workflow lines still need removing.

dev renders dev.yaml ALONE, via THERMOGRAPH_SECRETS_SKIP_COMMON=1. Eleven of
common.yaml's sixteen values are live production credentials, the render is a
plaintext concatenation consumed through env_file:, and dev is the operator's
desktop AND the Forgejo runner executing unreviewed dev-branch code with the
docker socket mounted. An override in dev.yaml would not help — last-wins
governs consumers, but prod's value is still physically a line in the file.
Verified: current renderer leaks 28 lines onto dev including both S3 keypairs
and the VAPID private key; patched gives exactly 12 keys and no prod credential.
Beta's render is byte-identical either way.

centralis.prod.yaml — its own file, its own renderer

/etc/centralis.env was hand-edited, which is how a JSON registry got written
unquoted into a shell-sourced file tonight and silently collapsed three
identities to one. The renderer now owns the file.

It is service- AND environment-scoped, not folded into prod.yaml, because
/etc/thermograph.env is loaded into every container in the app stack. Folding
these in would put CENTRALIS_AUTH_TOKEN and CENTRALIS_TOKENS — which
authenticate an endpoint carrying run_on_host and sql_query(write) — into the
web backend's environment, turning a read-anything bug in the app into a
foothold on the control plane.

Quoting is guaranteed three ways rather than assumed. Critically, `sops -d
--output-type dotenv` emits values verbatim, so reusing the existing render path
would have reproduced tonight's bug exactly. The new function decrypts to JSON
and emits POSIX single-quoted assignments; it then sources its own output in a
clean shell and compares every value before touching /etc; and it refuses to
write unless CENTRALIS_TOKENS parses as a non-empty subject->token object after
sourcing. Tested against 16 hostile values including embedded quotes, newlines
and ;rm -rf /.

Value-identity proven, not asserted: rendered vs live compared as *effective*
values on prod (a textual diff would report a false difference, and would report
a false match if the vault had captured quote characters as part of the value),
plus both env files producing a byte-identical `docker compose config` digest.
9 keys, both token subjects preserved. Nothing deployed.

Note for later: /etc/thermograph.env is also shell-sourced, and survives on raw
dotenv only because none of its 32 current values contains a shell-special
character. It is one quoted secret away from the same bug.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
2026-07-24 18:10:51 -07:00
emi
9350045866 Merge pull request 'Promote dev to main: vault consolidation + renderer fail-closed' (#76) from dev into main
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 7s
Sync infra to hosts / sync-prod (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 7s
2026-07-25 00:47:04 +00:00
emi
a00b2c016b Merge pull request 'secrets: factor shared values into common.yaml; stop the renderer failing open' (#75) from fix/key-gaps-digit-regex into dev
All checks were successful
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 8s
PR build (required check) / changes (pull_request) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 7s
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m5s
PR build (required check) / build-backend (pull_request) Successful in 1m14s
PR build (required check) / gate (pull_request) Successful in 2s
2026-07-25 00:44:51 +00:00
Emi Griffith
cb69a22c0f secrets: factor shared values into common.yaml; stop the renderer failing open
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 1m11s
PR build (required check) / build-backend (pull_request) Successful in 1m25s
PR build (required check) / gate (pull_request) Successful in 2s
Two changes to how credentials are stored and distributed.

1. common.yaml now exists.

The renderer has always concatenated common.yaml then <env>.yaml, host winning,
and the README has always documented common.yaml with a checkmark. The file was
never created. So every value shared by prod and beta was duplicated in both
vaults, free to drift apart with nothing to detect it.

16 values move to common.yaml: VAPID keypair, metrics token, IndexNow key,
REGISTRY_TOKEN, the S3 endpoint/bucket and both S3 keypairs, plus shared config.
5 stay per-host because their values genuinely differ (APP_CPUS, DB_CPUS,
DB_MEMORY, WORKERS, THERMOGRAPH_BASE_URL). 8 exist only on prod — the Discord
and mail credentials, which beta does not have at all.

Three more are held back deliberately despite being identical today:
POSTGRES_PASSWORD, THERMOGRAPH_AUTH_SECRET, THERMOGRAPH_DATABASE_URL. These are
the credentials that let one environment act as another, and beta is the more
exposed box — it serves public Forgejo and Grafana. They match only because beta
was seeded from prod. Keeping them per-host costs one line each and preserves
the ability to diverge; putting them in common.yaml would encode the equivalence
as intentional and make breaking it a migration rather than an edit. Reasoning
recorded in the vault README, since a future reader will otherwise "fix" it.

Done without any plaintext leaving prod: ciphertext was shipped up, decrypted
against the host's age key, recombined, re-encrypted, and shipped back. The
consolidation refuses to write unless it has proved merged(common + env) equals
the original env vault exactly — same keys, same values — and re-verifies from
the written files afterwards. Both checks passed for prod and beta.

2. render_thermograph_secrets no longer fails open.

One `return 0` covered two different situations: "this host is not configured
for SOPS" (true of the LAN dev box, and correct to succeed) and "this host IS
configured but its vault is not where we looked". The second is a failure, and
returning 0 made it a silent no-op wearing a success code — a deploy would
report success having rendered nothing, and the host would keep serving whatever
/etc/thermograph.env already held, including after a rotation.

The likely cause is passing the wrong root: the function wants the directory
containing deploy/secrets, which on the hosts is /opt/thermograph/infra, not
/opt/thermograph. That mistake looked exactly like "not configured here", which
is why it went unnoticed. It now exits 1 and names the probable cause.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
2026-07-24 17:40:26 -07:00
emi
f596fa87d8 Merge pull request 'Promote dev to main: NASA retirement' (#73) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 11s
secrets-guard / encrypted (pull_request) Successful in 12s
shell-lint / shellcheck (pull_request) Successful in 10s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m54s
Deploy backend to beta VPS / deploy (push) Successful in 2m24s
2026-07-24 23:59:00 +00:00
emi
005567dc51 Retire NASA POWER from every serving path (#72)
Some checks failed
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 12s
secrets-guard / encrypted (pull_request) Successful in 14s
PR build (required check) / changes (pull_request) Successful in 18s
shell-lint / shellcheck (pull_request) Successful in 14s
PR build (required check) / validate-observability (pull_request) Has been skipped
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m22s
Deploy backend to LAN dev server / build (push) Successful in 1m42s
PR build (required check) / build-frontend (pull_request) Successful in 1m19s
PR build (required check) / build-backend (pull_request) Successful in 1m33s
PR build (required check) / gate (pull_request) Successful in 1s
Deploy backend to LAN dev server / deploy (push) Failing after 2m0s
2026-07-24 23:56:34 +00:00
emi
ca84e0ce95 Promote dev → main (frontend QA batch → beta) (#71)
Some checks failed
Deploy frontend to beta VPS / deploy (push) Failing after 8s
Sync infra to hosts / sync-beta (push) Successful in 7s
Deploy backend to beta VPS / deploy (push) Failing after 13s
secrets-guard / encrypted (push) Successful in 12s
Sync infra to hosts / sync-prod (push) Successful in 14s
shell-lint / shellcheck (push) Successful in 12s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 57s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 1m2s
2026-07-24 23:20:16 +00:00
emi
6df4968679 Frontend QA batch: date/TZ, https origin, date-422, VAPID rotation, trace-precip, partial-day gate (#70)
Some checks failed
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
secrets-guard / encrypted (pull_request) Successful in 14s
PR build (required check) / changes (pull_request) Successful in 16s
PR build (required check) / validate-observability (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 28s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m58s
Deploy frontend to LAN dev server / build (push) Successful in 1m59s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 2m5s
Deploy backend to LAN dev server / build (push) Successful in 2m20s
PR build (required check) / build-frontend (pull_request) Successful in 1m44s
Deploy frontend to LAN dev server / deploy (push) Successful in 30s
PR build (required check) / build-backend (pull_request) Successful in 2m0s
PR build (required check) / gate (pull_request) Successful in 2s
Deploy backend to LAN dev server / deploy (push) Failing after 1m58s
2026-07-24 23:13:36 +00:00
emi
1c763a61c5 Merge pull request 'key-gaps: the key regex was blind to digits, inventing missing secrets' (#69) from fix/key-gaps-digit-regex into dev
All checks were successful
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 6s
2026-07-24 22:59:12 +00:00
Emi Griffith
f4313b5d87 Drop accidentally-committed worktrees; ignore .claude/worktrees
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 9s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-backend (pull_request) Successful in 44s
PR build (required check) / gate (pull_request) Successful in 3s
The previous commit swept in .claude/worktrees/city-resolver and
.claude/worktrees/thermograph-mentions as embedded git repositories. Those are
other Claude sessions' live checkouts and have no business in this tree.

Ignoring the directory so `git add -A` cannot do it again — the repo already
has a documented history of parallel sessions colliding through shared
checkouts, and this is the same hazard wearing a different hat.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
2026-07-24 15:57:34 -07:00
Emi Griffith
7ed375f32e key-gaps: the key regex was blind to digits, inventing missing secrets
`grep -oE "^[A-Z_]+="` cannot match a key name containing a digit. This estate
has exactly six such names — all the *_S3_* keys — so a prod audit returned 26
keys against 32 real ones, and reported the S3 and lake credentials as missing
from both live hosts.

They were present the whole time. The phantom was independently reproduced
twice by re-running the same pattern, which is what made it convincing, and it
was briefly recorded as one of two root causes of the lake being unqueryable
(#56). That issue has one cause: the missing duckdb-lake image.

An audit that under-reports is worse than no audit. A missing-secret finding
sends someone to provision a credential that already exists, and in a rotation
tool it would justify writing over one.

Fixed to `^[A-Z][A-Z0-9_]*=` in both the skill and key_gaps.py's docstring. The
match still stops at the `=`, so no value is read — that property is the reason
this grep exists rather than a parser.

Centralis's secrets_gaps carried the same bug and is fixed separately, with a
regression test.

Claude-Session: https://claude.ai/code/session_0182KTMrsTHJc3TcewCatJFY
2026-07-24 15:57:15 -07:00
emi
150029075e Merge pull request 'Promote dev to main: MET forecast coverage gate' (#64) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 9s
shell-lint / shellcheck (push) Successful in 16s
secrets-guard / encrypted (pull_request) Successful in 11s
shell-lint / shellcheck (pull_request) Successful in 13s
Deploy backend to beta VPS / deploy (push) Successful in 51s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m22s
2026-07-24 22:26:20 +00:00
emi
248eeee110 Gate MET forecast days on diurnal sample coverage (#63)
All checks were successful
secrets-guard / encrypted (push) Successful in 10s
shell-lint / shellcheck (push) Successful in 12s
secrets-guard / encrypted (pull_request) Successful in 10s
PR build (required check) / changes (pull_request) Successful in 14s
shell-lint / shellcheck (pull_request) Successful in 11s
PR build (required check) / validate-observability (pull_request) Has been skipped
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m41s
Deploy backend to LAN dev server / build (push) Successful in 1m48s
PR build (required check) / build-frontend (pull_request) Successful in 1m23s
PR build (required check) / build-backend (pull_request) Successful in 1m38s
PR build (required check) / gate (pull_request) Successful in 3s
Deploy backend to LAN dev server / deploy (push) Successful in 33s
2026-07-24 22:23:51 +00:00
emi
633c6a5ace Merge pull request 'Promote dev to main: first alerting + Postfix supervision' (#60) from dev into main
Some checks failed
Sync infra to hosts / sync-prod (push) Failing after 7s
Sync infra to hosts / sync-beta (push) Successful in 13s
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 6s
2026-07-24 20:49:08 +00:00