From 4cdea848cb4a88df6fd225d311d5753d80a96818 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Thu, 20 Aug 2026 11:42:06 -0700 Subject: [PATCH 1/4] ci: publish the documentation site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The site had no publication in this repository. Its pages are generated from the binary, committed, and gate-checked on every pull request — and then never left git. Measured against the live site today: the reference still describes `protection`, `minimum_generations` and `recovery_window`, keys that no longer exist; it lists seventeen error codes deleted this week; the capability page still says "no drill is run, and no service can currently prove a backup would restore"; and /guides/back-up-a-database is a 404. Documentation that is verified and unpublished is documentation nobody reads, and a published page describing a schema the binary rejects is worse than no page. Deploys to GitHub Pages from main on any change to the site or the generated reference, and on demand. main rather than a release tag: the reference is regenerated from the binary at HEAD and committed beside it, so main is where the documentation and the code it describes agree. The job runs the same build the gate runs — `astro check`, the build, and check-tables.mjs — and then refuses to publish if that build modified any committed page, because a site whose reference does not match the committed one is a repository state that should not reach main, let alone onebox.run. site/public/CNAME carries the custom domain into the artifact. Pointing the domain at Pages is a DNS change nobody can make from a workflow; until that happens this publishes to the github.io address and changes nothing for visitors. Co-Authored-By: Claude Opus 5 --- .github/workflows/site.yml | 94 ++++++++++++++++++++++++++++++++++++++ site/public/CNAME | 1 + 2 files changed, 95 insertions(+) create mode 100644 .github/workflows/site.yml create mode 100644 site/public/CNAME diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml new file mode 100644 index 00000000..16185321 --- /dev/null +++ b/.github/workflows/site.yml @@ -0,0 +1,94 @@ +# Publishing the documentation site. +# +# It used to have no publication at all. The pages were generated, committed, +# gate-checked on every pull request — and then never left the repository: the +# live site was serving a schema that had been renamed weeks earlier, a +# capability page describing a drill that by then existed, and a 404 for the +# backup guide. Documentation that is verified and unpublished is documentation +# nobody reads. +# +# It deploys from main rather than from a release tag. The generated reference +# is regenerated from the binary at HEAD and committed alongside it, so main is +# where the documentation and the code it describes agree; pinning publication +# to tags would republish whatever the last tag happened to carry and reopen the +# same gap on a slower clock. +name: Site + +on: + push: + branches: [main] + # The whole site is one build, but only these inputs can change it. + paths: + - "site/**" + - "docs/**" + - ".github/workflows/site.yml" + # Publishing the current main on demand, without an empty commit. + workflow_dispatch: + +permissions: + contents: read + +concurrency: + # One publication at a time, and a superseded one is cancelled rather than + # queued: the newer build already contains everything the older one had. + group: site + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v5.0.0 + with: + persist-credentials: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24.18.0 + cache: npm + cache-dependency-path: site/package-lock.json + + - name: Install dependencies + working-directory: site + run: npm ci + + # The same build the gate runs: `astro check`, the build itself, and + # check-tables.mjs. A page that would ship a table outside its scroll + # container fails here too, rather than being published because this + # workflow was in a hurry. + - name: Build + working-directory: site + env: + SITE_URL: https://onebox.run + run: npm run build + + # The published site must be the documentation for the binary it + # describes. The reference pages are generated and committed, and the gate + # fails a pull request that changes one without regenerating — so a build + # whose committed pages do not match is a repository state that should + # never reach main, and is not something to publish quietly. + - name: Refuse a site whose reference pages are not the committed ones + run: | + set -euo pipefail + if ! git diff --quiet -- docs site/src/content/docs; then + echo "the build modified committed documentation; publish is refused" >&2 + git --no-pager diff --stat -- docs site/src/content/docs >&2 + exit 1 + fi + + - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 + with: + path: site/dist + + deploy: + needs: build + runs-on: ubuntu-24.04 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/site/public/CNAME b/site/public/CNAME new file mode 100644 index 00000000..708e7bd4 --- /dev/null +++ b/site/public/CNAME @@ -0,0 +1 @@ +onebox.run From 1e5a65a5b0383728559aaf1896e10241c1723f5d Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Thu, 20 Aug 2026 11:56:53 -0700 Subject: [PATCH 2/4] ci: publish the documentation site to the Cloudflare project that owns it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onebox.run is a Cloudflare Pages project — `onebox`, with the custom domain already attached — published by hand: it lists no git provider, and the pages had drifted a long way behind the binary. The live reference named `protection`, `minimum_generations` and `recovery_window`, keys the binary now rejects. It listed seventeen error codes deleted this week. The capability page said no service could prove a backup would restore, after drills became executable. The backup guide was a 404. Publication is now something the repository does. On any change to the site or the generated reference, and on demand, the same build the gate runs — `astro check`, the build, and check-tables.mjs — is published to that project with `wrangler pages deploy`. Cloudflare rather than GitHub Pages because the domain, its certificate and its CDN are already there. Publishing into the path already in use needs one API token; moving to Pages would need a live DNS cutover and buy nothing. Before publishing it refuses a build that modified any committed page: a site whose reference does not match the committed one is a repository state that should not reach main, let alone onebox.run. That guard is what would have caught this drift. Needs CLOUDFLARE_API_TOKEN (Pages:Edit) and CLOUDFLARE_ACCOUNT_ID as repository secrets. Co-Authored-By: Claude Opus 5 --- .github/workflows/site.yml | 51 +++++++++++++++------------ .wrangler/cache/pages.json | 3 ++ .wrangler/cache/wrangler-account.json | 6 ++++ site/public/CNAME | 1 - 4 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 .wrangler/cache/pages.json create mode 100644 .wrangler/cache/wrangler-account.json delete mode 100644 site/public/CNAME diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 16185321..2e30289a 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -1,11 +1,18 @@ # Publishing the documentation site. # -# It used to have no publication at all. The pages were generated, committed, -# gate-checked on every pull request — and then never left the repository: the -# live site was serving a schema that had been renamed weeks earlier, a -# capability page describing a drill that by then existed, and a 404 for the -# backup guide. Documentation that is verified and unpublished is documentation -# nobody reads. +# onebox.run is a Cloudflare Pages project that was published by hand — the +# project lists no git provider — and the pages had drifted a long way behind +# the binary they describe: the live reference named `protection`, +# `minimum_generations` and `recovery_window`, keys the binary now rejects; it +# listed seventeen error codes that had been deleted; the capability page said +# no service could prove a backup would restore, months after drills became +# executable; and the backup guide was a 404. Generated, committed and +# gate-checked documentation that reaches nobody is documentation nobody reads, +# and a published page describing a schema the binary refuses is worse than no +# page at all. +# +# Publication is therefore something the repository does, not something someone +# remembers to do. # # It deploys from main rather than from a release tag. The generated reference # is regenerated from the binary at HEAD and committed alongside it, so main is @@ -35,7 +42,7 @@ concurrency: cancel-in-progress: true jobs: - build: + publish: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v5.0.0 @@ -76,19 +83,17 @@ jobs: exit 1 fi - - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 - with: - path: site/dist - - deploy: - needs: build - runs-on: ubuntu-24.04 - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 + # A branch name reaches Cloudflare as the deployment's branch, and the + # project treats one branch as production. main is what this workflow + # runs on, so the deployment is a production one by construction rather + # than by a flag somebody could forget. + - name: Publish + working-directory: site + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + npx --yes wrangler@4.123.0 pages deploy dist \ + --project-name onebox \ + --branch main \ + --commit-hash "${GITHUB_SHA}" diff --git a/.wrangler/cache/pages.json b/.wrangler/cache/pages.json new file mode 100644 index 00000000..ed9913dc --- /dev/null +++ b/.wrangler/cache/pages.json @@ -0,0 +1,3 @@ +{ + "account_id": "e6379d37f788129207ab5f566abfb4ff" +} \ No newline at end of file diff --git a/.wrangler/cache/wrangler-account.json b/.wrangler/cache/wrangler-account.json new file mode 100644 index 00000000..a9b7deb1 --- /dev/null +++ b/.wrangler/cache/wrangler-account.json @@ -0,0 +1,6 @@ +{ + "account": { + "id": "e6379d37f788129207ab5f566abfb4ff", + "name": "labstack" + } +} \ No newline at end of file diff --git a/site/public/CNAME b/site/public/CNAME deleted file mode 100644 index 708e7bd4..00000000 --- a/site/public/CNAME +++ /dev/null @@ -1 +0,0 @@ -onebox.run From 99dbf5acbcd035510d8ca6426181aca5cf239ef6 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Thu, 20 Aug 2026 11:58:48 -0700 Subject: [PATCH 3/4] docs: the errors page no longer has reserved rows to explain The page summary still told readers that "a row marked reserved is one no path raises yet", describing a column that went with the nineteen unreachable codes. It now says what the table means: every code in it is raised by a path in the shipped binary. Co-Authored-By: Claude Opus 5 --- cmd/ob-docgen/main.go | 2 +- site/src/content/docs/reference/errors.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/ob-docgen/main.go b/cmd/ob-docgen/main.go index b5b82dc5..4ba14827 100644 --- a/cmd/ob-docgen/main.go +++ b/cmd/ob-docgen/main.go @@ -818,7 +818,7 @@ func jsonString(s string) string { func renderErrorPage() string { var buf bytes.Buffer - summary := "Every typed failure code in the contract: the loader's validation codes and the CLI and engine's operation codes, all of which are reachable, and the lifecycle failure contract, where a row marked reserved is one no path raises yet." + summary := "Every typed failure code in the contract: the loader's validation codes, the CLI and engine's operation codes, and the lifecycle failure contract. Every one of them is raised by a path in the shipped binary." fmt.Fprintln(&buf, "---") fmt.Fprintln(&buf, `title: Error codes`) diff --git a/site/src/content/docs/reference/errors.mdx b/site/src/content/docs/reference/errors.mdx index 01c78d52..bda83acc 100644 --- a/site/src/content/docs/reference/errors.mdx +++ b/site/src/content/docs/reference/errors.mdx @@ -1,7 +1,7 @@ --- title: Error codes -description: "Every typed failure code in the contract: the loader's validation codes and the CLI and engine's operation codes, all of which are reachable, and the lifecycle failure contract, where a row marked reserved is one no path raises yet." -summary: "Every typed failure code in the contract: the loader's validation codes and the CLI and engine's operation codes, all of which are reachable, and the lifecycle failure contract, where a row marked reserved is one no path raises yet." +description: "Every typed failure code in the contract: the loader's validation codes, the CLI and engine's operation codes, and the lifecycle failure contract. Every one of them is raised by a path in the shipped binary." +summary: "Every typed failure code in the contract: the loader's validation codes, the CLI and engine's operation codes, and the lifecycle failure contract. Every one of them is raised by a path in the shipped binary." status: shipped generated: true sidebar: From dc735ca941e3f8bf22b42bd64fba9dfb2c7a7565 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Thu, 20 Aug 2026 12:06:41 -0700 Subject: [PATCH 4/4] ci: name the Cloudflare account inline, leaving one secret to install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An account id is an identifier, not a credential — it is what sits in a committed wrangler.toml on every project that has one. Carrying it as a repository secret bought no secrecy and doubled what somebody has to set up before the site can publish itself. CLOUDFLARE_API_TOKEN, scoped to Pages:Edit, is now the only thing this workflow needs that is not in the repository. Co-Authored-By: Claude Opus 5 --- .github/workflows/site.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 2e30289a..23685704 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -91,7 +91,11 @@ jobs: working-directory: site env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + # An account id is an identifier, not a credential — it is what sits in + # a committed wrangler.toml on every project that has one. Keeping it + # here rather than in a secret leaves exactly one secret to install, + # and makes it obvious which account this publishes to. + CLOUDFLARE_ACCOUNT_ID: e6379d37f788129207ab5f566abfb4ff run: | npx --yes wrangler@4.123.0 pages deploy dist \ --project-name onebox \