Session persona isolation
Existing sessions now use a dedicated play_sessions.session_persona snapshot (persona ID plus public prompt fields, or explicit no-persona). Account/world choices only seed new sessions. The in-chat persona manager updates only its session; editing a profile requires reselecting it to refresh that session. Private persona notes are never copied into prompts.
Legacy sessions initialize once from their saved persona metadata, using a conditional update so a concurrent explicit selection wins. Missing metadata stays no-persona: names are not used to guess an ID. This cannot reconstruct an identity already overwritten before the fix; users can explicitly select the intended persona in that session.
Send, regenerate, continue and restart read the session snapshot. Revert/checkpoint restore cannot change the binding; branches inherit it. Reads overlay the snapshot onto display metadata, keeping displayed and generated identities consistent. No existing messages, memories, creator edits or world defaults are bulk rewritten.
Deployment
Apply packages/server/drizzle/0053_session_persona.sql to the target database before deploying this code (with a bounded lock timeout and retry if the table is busy). This is an additive nullable JSONB column, with no bulk backfill. The delayed schema self-heal also includes this column, but is not a substitute for pre-deployment migration. Rollback can leave the unused column in place; old application code will resume its old persona behavior.
Validation
Run on an ephemeral PGlite database only, with DATABASE_URL and DATABASE_READ_URL empty, PGLITE_DATA_DIR=memory://, a test BETTER_AUTH_SECRET, and REDIS_URL/POSTHOG_API_KEY empty:
pnpm --filter @yumina/server exec tsx --test src/lib/session-persona.test.ts src/lib/persona-metadata.test.ts src/lib/persona-prompt.test.ts
pnpm --filter @yumina/server exec tsx --import ./scripts/test-session-persona-bootstrap.mts --test --test-force-exit src/routes/sessions-branch.test.ts src/routes/sessions-revert.test.ts
pnpm build
pnpm typecheckThe bootstrap creates synthetic tables from the Drizzle column definitions, without production constraints/indexes; these tests validate resolver/route behavior, not production migration locking. The PGlite client is closed after the tests to avoid its normal runtime exit status leaking into the test runner.
