Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
This is the Saturday documentation site, built with **Mintlify**.
Dev server: `mintlify dev` (or check `package.json` scripts).

A page naming endpoints, fields or webhook events: run `scripts/check-docs-drift.py` locally before merge (README, "Local checks"), against a fuel-backend worktree at `origin/main`, since the shared checkout is often on another branch. No PR check here can run it; fuel-backend's hourly Docs Drift catches it after merge.

<!-- MICHELLE-VISUAL-VERIFY-START -->
## Visual Verification Mandate

Expand Down
92 changes: 21 additions & 71 deletions .github/workflows/docs-drift.yml
Original file line number Diff line number Diff line change
@@ -1,104 +1,54 @@
name: Docs Drift

# Docs-vs-API drift gate, docs-homed copy.
# Docs-vs-API drift, docs-repo half.
#
# The published docs are what a partner builds against before they ever call us.
# When the backend renames a field, retires an endpoint or changes an event
# name, nothing noticed that the docs still described the old behaviour, and the
# drift got found by a customer whose integration broke. This makes the
# mechanical half of that impossible rather than something a reader might catch.
# drift got found by a customer whose integration broke.
#
# This copy fires on a DOCS pull request and checks it against fuel-backend@main:
# it catches a page that describes an endpoint, field or event the API does not
# have. A mirror workflow of the same name lives in fuel-backend and fires on a
# BACKEND pull request, checking it against docs@main, which catches the other
# and more dangerous direction: an API change that leaves the docs stale. Neither
# repo can path-trigger on the other's commits, so both copies are required; the
# same reason calculator-crossval.yml is mirrored between fuel-backend and
# saturday, and the sibling-clone mechanics below are lifted from it.
# The drift check itself does not run here. It reads fuel-backend's private
# source, and this repository is public: the org's self-hosted runners refuse
# public repositories (a stranger's pull request would run on alex-desktop), and
# a fuel-backend credential in this repo's settings would sit one leaked token
# away from the engine source (issue #36). So fuel-backend's Docs Drift workflow
# runs the check on every backend pull request against docs@main, and hourly
# against docs main and fuel-backend main, which catches a merged page that names
# an endpoint, field or event the API does not have within about an hour. Before
# merging a page like that, run the check locally (README, "Local checks").
#
# The checker and the baseline live HERE, in the docs repo, in one copy. The
# fuel-backend mirror clones this repo and runs this same script, so the two
# sides cannot disagree about what drift means.
# The checker and the baseline live HERE, in one copy. fuel-backend clones this
# repo and runs this script, so there is one definition of drift.
#
# WHAT A GREEN RUN DOES NOT PROVE: that the numbers are right. Every check here
# is structural. A page stating a default of 60 g/hr against code that says 90
# passes cleanly, and that is the class of error that prompted this gate. The
# structural half is what is mechanically decidable today; see the script header
# for what closing the rest would take.
# WHAT A GREEN RUN DOES NOT PROVE: that the numbers are right. Every check is
# structural. A page stating a default of 60 g/hr against code that says 90
# passes cleanly. See the script header for what closing that would take.
#
# ORDERING HAZARD, stated because it will happen. A rename that spans both repos
# cannot make both sides green at the same instant: the docs cannot name a field
# that fuel-backend@main does not have yet, and the backend cannot remove one the
# docs still cite. Removals sequence cleanly, docs first, because deleting a
# mention never introduces drift. A rename does not. Both copies are advisory
# until marked required in branch protection, so the human lands both and main
# goes green; do not "solve" it by widening the baseline, which is what the
# shrink-only job exists to prevent.
#
# Runs on Saturday's self-hosted runner on alex-desktop, off the GitHub Actions
# quota. Standard library python, no Mintlify build, no node install: ~0.2s.
# mention never introduces drift. A rename does not: land both, and the hourly
# run goes green once both are on main. Do not "solve" it by widening the
# baseline, which is what the job below exists to prevent.

on:
pull_request:
branches: [main]
paths:
- '**.mdx'
- 'api-drift-baseline.txt'
- 'scripts/check-docs-drift.py'
- '.github/workflows/docs-drift.yml'
push:
branches: [main]
paths:
- '**.mdx'
- 'api-drift-baseline.txt'
- 'scripts/check-docs-drift.py'
workflow_dispatch:

concurrency:
group: docs-drift-${{ github.ref }}
cancel-in-progress: true

jobs:
drift:
runs-on: self-hosted
timeout-minutes: 10
steps:
# Both repos as siblings INSIDE the workspace, the layout
# calculator-crossval.yml uses, so nothing is written outside it.
- name: Checkout docs (this PR)
uses: actions/checkout@v5
with:
path: docs

# Self-hosted runner, so the box's own authenticated SSH clones the
# private repo and no cross-repo PAT secret is needed. Shallow, because
# only the working tree is read.
- name: Checkout fuel-backend@main (sibling)
run: |
set -euo pipefail
rm -rf fuel-backend
git clone --depth 1 --branch main \
git@github.com:SaturdayInc/fuel-backend.git fuel-backend
echo "fuel-backend at: $(git -C fuel-backend rev-parse --short HEAD)"

- name: Check the docs still describe the live API
run: |
python3 docs/scripts/check-docs-drift.py \
--docs docs --backend fuel-backend --list

# The baseline records drift that predates the gate so unrelated PRs are not
# blocked by it. That only holds while it can shrink and not grow: a baseline
# a PR may append to is an off switch, and the next real regression gets
# written into it instead of reported. Removing entries is the point and is
# always allowed.
#
# GitHub-hosted, unlike the drift job: this one reads only this public repo, and
# the org's self-hosted runners refuse public repositories, so on self-hosted it
# queued until cancelled and never ran (issue #36; engine-constants.yml has the
# full reasoning). Hosted minutes are free for public repos.
# GitHub-hosted: this reads only this public repo, and hosted minutes are free
# for public repos (engine-constants.yml has the full reasoning).
baseline-shrink-only:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand Down
2 changes: 1 addition & 1 deletion DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ To verify a DNS change: `dig docs.saturday.fit CNAME` answers `cname.mintlify-dn

Mintlify deploys from Git: a merge to `main` rebuilds the site. A rebuild can also be triggered by hand from [dashboard.mintlify.com](https://dashboard.mintlify.com) on the Saturday API project.

Two checks run on every pull request from `.github/workflows/`. Engine Constants (GitHub-hosted) fails the PR if a page prints an engine value. Docs Drift asks for the self-hosted runner, which does not accept jobs from a public repository, so it queues until it is cancelled: run it locally instead (README, "Local checks") and cancel the queued run. The fuel-backend copy of the same check runs on backend pull requests and catches the other direction.
Engine Constants (GitHub-hosted) runs on every pull request and fails it if a page prints an engine value. Docs Drift here only keeps `api-drift-baseline.txt` from growing. The drift check itself needs fuel-backend's private source, so it runs from fuel-backend: on every backend pull request, and hourly against both repos' `main`. A merged page that names an endpoint, field or event the API lacks is caught within about an hour and routed to the SRE sweep. Run the check locally before merging such a page (README, "Local checks").

## Site configuration

Expand Down
Loading