From ef8521b9fb9f96dcb778f72842d2a2471eccf9d8 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Mon, 17 Aug 2026 12:58:17 +0300 Subject: [PATCH] fix(ci): prerender the web bundle after the api/worker deploy, not beside it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build-web` had no `needs:`, so it ran in parallel with the binary build and both deploy jobs. The prerender fetches VITE_API_BASE at build time, which means the job baked whatever the api happened to be serving at the moment it ran — old binary or new, depending on which runner finished first. Both outcomes showed up on consecutive runs. Run 68 won by ~25 seconds and the repo-visibility fix reached the api and the crawler snapshot together. Run 69 lost: the api served the new activity/summary private aggregate while the baked /activity/ snapshot had none, so the 15 August card read "47 changes in 3 repositories" where the api said 54 with 7 private. Browsers hydrate and refetch, so a visitor saw the right page — but curl, which is what crawlers and AI screeners get, saw the stale one until refresh.yml was dispatched by hand. Nothing in the pipeline noticed. deploy-worker is in the needs list alongside deploy-api because the worker owns migrations: a schema change isn't live until it has restarted, so an api that depends on one isn't answering correctly before then either. The graph is now serial — binaries -> api/worker -> web — which costs the overlap between the web build and the binary build. Worth it: the alternative is a published snapshot whose correctness depends on runner scheduling, failing silently and for a whole day. Job graph verified acyclic with every `needs:` resolving to a real job. Closes https://git.lair.cafe/grenade/moments/issues/8 --- .gitea/workflows/deploy.yml | 13 +++++++++++++ CLAUDE.md | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index f44795f..366813d 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -101,6 +101,19 @@ jobs: build-web: name: Build prerendered web + # The prerender fetches VITE_API_BASE at build time, so this job bakes + # whatever the api is serving when it runs. Without these dependencies it + # raced the api/worker deploys and the crawler snapshot silently disagreed + # with the live api for a whole day, until the nightly refresh: it won the + # race once (a visibility fix reached both together) and lost it once (an + # api field the snapshot then lacked). deploy-worker is in the list too, + # not just deploy-api — the worker owns migrations, so a schema change + # isn't applied until it has restarted. + # + # The cost is a serial deploy: binaries -> api/worker -> web. Worth it; + # the alternative is a snapshot whose correctness depends on which runner + # finished first. + needs: [deploy-api, deploy-worker] runs-on: fedora-44 steps: - uses: actions/checkout@v4 diff --git a/CLAUDE.md b/CLAUDE.md index 81c4c2e..b81f296 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,6 +119,13 @@ CI-driven via **Gitea Actions** (`.gitea/workflows/`), the source of infra truth prerendered web bundle, then deploy each component over SSH as the `gitea_ci` user with scoped sudo (`asset/sudoers.d/`). Services run under systemd with hardened units; the api/worker reach postgres over mTLS using the host cert. + The job graph is deliberately serial — binaries → api/worker → web — because + the prerender fetches `VITE_API_BASE` at build time and so bakes whatever the + api is serving when `build-web` runs. `build-web` therefore `needs:` both + deploy jobs (the worker owns migrations, so a schema change isn't live until + it restarts). When it ran in parallel instead, whether the crawler snapshot + matched the api came down to which runner finished first, and a lost race + meant a stale snapshot until the nightly refresh. - `refresh.yml` — daily `schedule:` (+ manual): rebuilds and redeploys only the web tier, re-baking the prerendered crawler snapshot from the current gist (CV) and activity API without bouncing the api/worker.