2026-05-08

The postgame replay work changed one label and broke CI.

The intended runtime behavior was reasonable. Completed Bichess game summaries should show a readable engine participant name like Random Legal v1, not a raw engine version id like builtin-random-legal. That looks like UI polish. It was actually a persistence-contract change.

The failure only showed up in the GitHub Actions node job. Local server tests without Postgres skipped the DB-backed persistence assertions by design. CI had Postgres available, so it exercised durable game summaries and caught two exact expectation mismatches: the persisted participant displayName no longer matched the old raw id.

That was the useful part of the incident. The code had not broken the product; the tests were still asserting the previous data contract. But the route by which it failed showed the boundary: participant attribution lives in completed game records, list APIs, and replay surfaces. Once a label is written there, it is not merely copy on the page.

The fix was small: update the two persistence test expectations to the new engine label and document the guardrail. Future changes touching engine/player display names, GameSummary, GameParticipant, recordGameEnd, completed game lists, or fallback participant attribution need the Postgres-backed test path, not only the no-DB shorthand.

This is exactly why identity work in Bichess now separates display identity, authority, ownership, and public profile. One identifier should not quietly do all four jobs. In this case the problem was narrower, but the shape was the same: a value that felt presentational was also part of the durable record.

If a label is persisted, indexed, or replayed, changing it is a data-model change. The UI may be the first place humans notice it, but the database is where the contract lives.