docs(workflows): clarify that GitHub only builds and serves while Gitea refreshes sources
CI / Typecheck, test, feed (push) Successful in 20s
CI / Build site (push) Successful in 11s
CI / Container image (push) Successful in 40s

Document the forge responsibilities across AGENTS.md, README.md, ARCHITECTURE.md, INGESTION.md, and the CI workflow headers: GitHub Actions only builds and serves (Pages deployment) without fetching data sources, while Gitea Actions exclusively runs the scheduled refresh and publishes the container image.
This commit is contained in:
Lucas Winther
2026-09-18 10:14:37 +02:00
parent 6f5b9144fd
commit f07607911b
7 changed files with 37 additions and 15 deletions
+5
View File
@@ -1,4 +1,9 @@
name: CI
# Gitea Actions CI/CD
#
# Runs typecheck, tests, and feed sanity checks; builds the site; and publishes
# the container image to Gitea's registry. Scheduled source refreshes are
# handled by .gitea/workflows/refresh.yml.
on:
push:
+5
View File
@@ -1,4 +1,9 @@
name: CI
# GitHub Actions CI/CD
#
# GitHub only needs to build and serve (deploying to GitHub Pages), not refresh
# data sources. Scheduled fetching and snapshot refresh run exclusively on
# Gitea Actions (.gitea/workflows/refresh.yml).
on:
push:
+5 -2
View File
@@ -41,8 +41,11 @@ progress on events that repeat daily, and lets a user mark events completed.
twenty games, the full interface, offline support, a static server, a Docker image and CI all exist and
are tested. The refresh runner (`bun run refresh`) fetches, caches raw snapshots and rebuilds the
feed; `.gitea/workflows/refresh.yml` runs it twice a day and commits only when a page actually
changed. The SQLite layer and the review queue are still specified in `docs/` but not built, so the
feed is a static JSON file built from snapshots, falling back to checked-in fixtures.
changed. GitHub only needs to build and serve (deploying to GitHub Pages via
`.github/workflows/ci.yml`), not refresh data sources; scheduled fetching and container image
publishing run exclusively on Gitea Actions. The SQLite layer and the review queue are still
specified in `docs/` but not built, so the feed is a static JSON file built from snapshots,
falling back to checked-in fixtures.
## Three constraints that shape everything
+6 -7
View File
@@ -388,13 +388,12 @@ pipeline always means the code changed rather than a wiki being down.
### Refreshing the data
Gitea Actions only — the GitLab and GitHub pipelines run the gates / builds, but do not fetch wikis.
`.gitea/workflows/refresh.yml` runs `bun run refresh` twice a day (and on demand, with a dry-run
input). It fetches each source at most once per cycle, and **commits only when a page's bytes
actually changed** — a `304`, an identical body, or a fetch that fails to parse all leave the
working tree clean and produce no commit. When something did change it commits the raw snapshots and
dispatches `ci.yml`, which typechecks, tests, rebuilds the feed and republishes the image through the
path that already existed; none of that logic is duplicated.
Gitea Actions only — GitHub Actions only needs to build and serve (deploying to GitHub Pages),
and never refreshes data sources. `.gitea/workflows/refresh.yml` runs `bun run refresh` twice a
day (and on demand, with a dry-run input). It fetches each source at most once per cycle, and
commits the snapshots when a page's bytes actually changed. On every non-dry run, it dispatches
`ci.yml` so that updated cache confirmations or new snapshots trigger a feed rebuild and
container image publication; none of that logic is duplicated.
That dispatch is a REST call rather than `gh workflow run`, there being no `gh` on a Gitea runner.
It is also, strictly, belt and braces: unlike GitHub, Gitea does **not** suppress workflow triggers
+3 -1
View File
@@ -224,7 +224,9 @@ that ordering is load-bearing.
`Dockerfile` builds and serves this; the image runs typecheck and tests during build, ships no source
or toolchain, and runs unprivileged. `.gitea/workflows/ci.yml` and `.gitlab-ci.yml` run the same
gates and publish it, while `.github/workflows/ci.yml` builds and deploys to GitHub Pages.
gates and publish it, while `.github/workflows/ci.yml` builds and deploys to GitHub Pages. GitHub
only needs to build and serve, not refresh data sources; the scheduled refresh and container image
publishing run exclusively on Gitea Actions.
### Hosting under a subpath
+6 -5
View File
@@ -394,11 +394,12 @@ annotated on the run page, listed in the job summary with its status code, and c
conduct for why that ordering is load-bearing.
**Built: `scripts/refresh-sources.ts`** (`bun run refresh`), scheduled by
`.gitea/workflows/refresh.yml`. It takes its adapters, store, robots gate, fetch and clock by
injection, so the whole runner is tested offline against a fake fetch. A fetched body is *rejected*
— the previous snapshot survives — when it fails `canParse`, throws, or yields zero events; storing
an empty parse would make the feed build prefer it over the fixture and silently empty a game's
calendar.
`.gitea/workflows/refresh.yml`. Scheduled fetching and data refresh run exclusively on Gitea
Actions; GitHub Actions only builds and serves, and never refreshes data sources. The runner
takes its adapters, store, robots gate, fetch and clock by injection, so the whole runner is
tested offline against a fake fetch. A fetched body is *rejected* — the previous snapshot survives
— when it fails `canParse`, throws, or yields zero events; storing an empty parse would make the feed
build prefer it over the fixture and silently empty a game's calendar.
The one exception is a page that states its own emptiness: when a parser's `statesNoEvents` says the
document itself reports listing nothing, the empty parse is stored as that source's real answer and
+7
View File
@@ -1130,6 +1130,13 @@ describe("the workflows that drive the refresh", () => {
expect(githubCi).toContain("deploy-pages");
expect(githubCi).toContain("pages:");
});
test("GitHub only builds and serves and has no refresh workflow", async () => {
const githubRefresh = Bun.file(
new URL("../.github/workflows/refresh.yml", import.meta.url),
);
expect(await githubRefresh.exists()).toBe(false);
});
});
describe("flags", () => {