# Continuous integration. # # Two jobs, split by language, because they prove different things and the # Python one is the slow half. # # What this actually gates, in order of how likely each is to catch something: # # 1. The two implementations of the game agree. `engine.py` and `engine.ts` # score every ordered pair of words in the answer list — 4,603^2 = 21.2M # feedback patterns — and their SHA-256 digests must match each other and # the committed value. The page claims it VERIFIES a recorded run in your # browser; that claim is only worth something if the browser's scorer and # the environment's scorer cannot drift. # 2. The reward measures something. `probe.py` plays seven deliberately # crafted policies and asserts the reward orders them the way a person # would, PER WEIGHTED COMPONENT — a component that is flat across every # policy is measuring nothing and still moves the total. It also asserts # neither good policy dominates the other, because the reward editor on # the site is a claim about a real trade-off. # 3. Every committed fixture replays through the engine and reproduces its # own recorded rewards. A fixture that cannot be regenerated is a claim # with no receipt behind it. # 4. The word lists rebuild byte-identically from their committed sources. # If they do not, the conformance digest is describing a different game. # 5. The demo contract holds — see scripts/check-demos.mjs. # 6. Four things that fail SILENTLY in production and nowhere else: # prerendered routes carrying their own
, sitemap.xml existing, # no blob-backed worker, and the bundle budget. # # NO MODEL IS EVER CALLED. Rollouts are captured by hand and committed; a gate # that cost a paid inference call would be switched off within a month. # # This runner is aarch64 and configured with `container.network: host`. Nothing # here needs a service container, so the traps that cost PIG three failed runs # do not apply — but do not add `services:` here without reading that repo's # workflow first. name: ci on: push: branches: [main] pull_request: jobs: web: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' # Corepack ships with Node and installs the exact pnpm pinned by # `packageManager`, so CI and a laptop run the same version. The download # prompt is disabled because a non-interactive runner cannot answer it and # would otherwise hang until the job times out. - name: Enable pnpm env: COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' run: | corepack enable corepack prepare --activate test "$(pnpm -v)" = "11.21.0" || { echo "pnpm $(pnpm -v) != 11.21.0"; exit 1; } - name: Install env: CI: 'true' run: pnpm install --frozen-lockfile # `pnpm build` ends in the prerender pass, which drives a real browser. # Without this the build fails on its very last action, after everything # else has gone green — the most expensive place to find a missing # dependency. - name: Install chromium run: pnpm exec playwright install --with-deps chromium - run: pnpm typecheck - run: pnpm check - run: pnpm test - run: pnpm build - run: node scripts/bundle-budget.mjs # Crawlers do not run JavaScript. Without a real per-route HTML file every # shared link previews as the homepage, and the failure is invisible from # inside the app. - name: The prerendered head is real run: | test -f dist/demos/wordle/index.html || { echo "no prerendered demo route"; exit 1; } grep -q 'og:title' dist/demos/wordle/index.html || { echo "og tags missing"; exit 1; } grep -q '