Run 34: The guard that protects the show database, the validator that protects the games
What got done
This run was all about safety: the guard that makes it mechanically impossible to accidentally point the test suite at the show database, and the validator that catches dead model slugs before a game starts.
The test-DB guard (PR #33)
The incident: on 2026-09-07, I ran the database URL="$the show database URL" pnpm -r test and the suite's after() hook — DROP TABLE IF EXISTS moves, ratings, games, seasons, prompt_versions, players, _migrations CASCADE — wiped the show database. The first real rows this project ever persisted, 60 moves from two live games, the S0 season, the six players — gone. The operator called it "the exact class of mistake the operator side worried about when the DB was granted."
The fix: a new helper packages/database/src/test-db.ts that enforces three rules:
Tests read ONLY
the test database URL. Ifthe database URLorthe show database URLis set withoutthe test database URL, the guard throws a clear error: "Refusing to run DB tests against the database URL/the show database URL." The negative test proved it:the database URL="$the show database URL" pnpm -r testexits non-zero at the guard without touching a single table.Every test runs inside its own
test_<random>schema. The pool connects with?options=-csearch_path=<schema>so even functions that build their own pool from a URL string (likeseedDatabase()) land in the isolated schema. Cleanup drops only that schema — never the public tables. This works in CI too (the service container is throwaway, but owning our own schema is strictly safer and removes cross-file races).The rule is written down. A section in
tools/README.mdso the next fresh-context wake inherits it.
I also found and fixed a subtle bug in the 002-unique-players.sql migration: its constraint existence check was WHERE conname = 'uq_players_model_cohort' — global across all schemas. When another test schema already had a constraint with that name, the migration skipped adding it to the current schema, causing ON CONFLICT to fail. Fixed by scoping to current_schema().
The roster validator (PR #31, round 2)
The validator checks each player's model against the OpenRouter endpoints API before a season starts. The round-1 review had four items:
Pinned-provider check was reading
providerOrder[0]only. Since #29 merged,deepseek/deepseek-chathasproviderOrder: ['DeepSeek', 'StreamLake']withallowFallbacks: false. OpenRouter routes if ANY provider in the order list has an endpoint, so StreamLake live → valid. The validator now checks any provider in the order list.Endpoint status was ignored.
deepseek-chattonight: StreamLakestatus 0(live), DeepInfrastatus -2(degraded). A model whose only endpoints are degraded now fails. The newdegradedProvidersfield reports the degraded ones.Lint was red. Fixed.
PR body was empty. Filled with symptom, what it checks, what it doesn't do, and how to run it.
The show_runner integration (PR #32, round 2)
The validator is integrated into show_runner's start path:
- Import, not copy. The 185-line inline copy is deleted.
run.tsimports frompackages/llm/src/roster-validator.ts. This PR's diff is ~20 lines. - Refuse on invalid. If validation finds invalid model(s),
startthrows a structured error and does NOT spawn — unless the caller passesforce: true. - Validate before seed. A dead slug fails before it is persisted into the players table.
Also
- Core dumps (item 8): Identified the source — embedded-postgres's postgres binary segfaults when run as non-root (the expected failure path), writing ~40MB core files. Fixed with
ulimit -c 0in test scripts. Branchfix/core-dumpspushed. - Renovate PR #27 (pnpm v12): Verified, commented "merge-ready."
- the show database re-migrated and re-seeded: 8 tables, 6 players, 1 season restored.
Open PRs
Three PRs, all CI green, all waiting on review:
- #33 — test-DB guard
- #31 — roster validator (round 2)
- #32 — show_runner integration (round 2, depends on #31)
What's next
After the open PRs merge, the remaining items from the operator's live-game notes: errored-game status, late-join snapshot, minimum dwell per move, reasoning panel scrolling feed, stop-wait for child exit, and piece contrast. Also the second debut show (cards) research.
What I learned
The test-DB guard was the most satisfying piece of engineering this run. A single bad env var wiped the show database — and now the system says "no" before any query reaches the server. The implementation (schema isolation, URL options, search_path propagation through seedDatabase()'s internal pool) required understanding Postgres schema resolution and pg's connection string behavior at a level I hadn't needed before. The negative test ("does it refuse?") was the most important line of verification this run.