Files
PIG-Demo/.github/workflows/ci.yml
T
karti-ai 331b46b114 Make the page playable: interactive board, live solver, and a usable run picker
Three things the build was quietly missing.

The interactive board was never mounted. The contract has `interactive.init` and
`interactive.Controls`, the demo implemented both, and the shell's `split-play`
beat rendered only the replay — so the beat titled "you and the model get the
same word" showed one board. PlayYourself now renders the visitor's attempt from
the same seed as the run beside it, generically: it knows only the contract, so
any demo shipping an interactive mode gets it and one that does not renders
nothing rather than an empty pane.

solver.worker.ts was dead code — nothing constructed it, which is how CI caught
it: `new Worker(` appeared nowhere in the bundle. It is wired now behind "what
would the best player guess?", and it answers in 92ms from a real worker on
boards no recording covers. That is the difference between a demo and a video.
It also surfaces the moment the solver picks a word that CANNOT win, which is
the counterweight visible in one line instead of explained in a paragraph.

The CI check that found it was itself wrong: it grepped every bundled file for
`blob:`, which React's own code contains in a scheme check, so it failed on a
risk that was not present. It now greps for worker construction from a blob,
which is the thing production CSP actually blocks in silence.

And the run switcher was thirty buttons carrying four distinct labels. Split
into arm and seed, holding the seed across an arm change — comparing two agents
means comparing them on the same hidden word, and silently jumping seeds would
break that while looking fine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 16:40:12 -07:00

102 lines
4.0 KiB
YAML

name: ci
on:
push:
branches: [main]
pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with: { version: 11.21.0 }
- uses: actions/setup-node@v5
with: { node-version: 22, cache: pnpm }
# Asserts the toolchain matches what package.json pins, rather than
# discovering a mismatch three steps later as an unrelated build error.
- name: preflight
run: |
test "$(pnpm -v)" = "11.21.0" || { echo "pnpm $(pnpm -v) != 11.21.0"; exit 1; }
- run: CI=true pnpm install --frozen-lockfile
# `pnpm build` ends in the prerender pass, which drives a real browser.
# Without this the build fails at the very last step, after everything
# else has passed, with a Playwright message about installing browsers.
- 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
# The prerender pass writes a real HTML file per route. Crawlers do not
# run JavaScript, so without these every shared link previews as the
# homepage — assert the baked tags actually landed.
- name: 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 -qv 'PIG Demo — RL environments you can play</title>' dist/demos/wordle/index.html \
|| { echo "demo route kept the homepage title"; exit 1; }
test -f dist/404.html || { echo "no 404.html"; exit 1; }
# A blob-backed worker is blocked in production and nowhere else: the
# site's CSP has no worker-src, so it falls back to default-src 'self'.
# The failure is silent — the worker simply never boots.
#
# Grep for worker construction FROM a blob, not for the string `blob:`
# anywhere. React's own bundle contains that string in a URL-scheme check,
# so the broad version fails on a risk that is not present, which teaches
# everyone to ignore it.
- name: no blob-backed workers
run: |
if grep -rEo "new (Shared)?Worker\([^)]{0,80}" dist/assets/*.js \
| grep -E "blob:|createObjectURL"; then
echo "a worker is constructed from a blob URL; production CSP blocks it silently"
exit 1
fi
echo "ok — no blob-backed worker construction in the bundle"
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v6
with: { enable-cache: true }
- run: uv sync --all-packages
# Rebuild the word lists from their committed sources and assert the
# output is byte-identical. If it is not, every downstream number —
# the conformance digest included — is describing a different game.
- name: word lists rebuild identically
run: |
uv run python envs/wordle_five/words/build_words.py
git diff --exit-code envs/wordle_five/words/*.json
- run: uv run pytest envs/wordle_five/tests -q
- run: uv run python envs/probe.py
# The cross-language gate. Both halves score all 21.2M (guess, answer)
# pairs; the digests must match each other and the committed value.
- uses: pnpm/action-setup@v4
with: { version: 11.21.0 }
- uses: actions/setup-node@v5
with: { node-version: 22, cache: pnpm }
- run: CI=true pnpm install --frozen-lockfile
- run: pnpm conformance
# Every committed fixture must replay through the Python engine and
# reproduce its own recorded rewards. A fixture that cannot be
# regenerated is a claim with no receipt behind it.
- run: uv run python envs/verify_fixtures.py