Skip to content

fix(rooms): mutações de sala param de responder ok:true sem confirmar a escrita (#56) - #96

Merged
caioross merged 1 commit into
mainfrom
auto/issue-56-honest-room-writes
Jul 26, 2026
Merged

fix(rooms): mutações de sala param de responder ok:true sem confirmar a escrita (#56)#96
caioross merged 1 commit into
mainfrom
auto/issue-56-honest-room-writes

Conversation

@caioross

Copy link
Copy Markdown
Owner

Contexto

Em src/app/api/rooms/[code]/route.ts, quatro das cinco actions faziam
await sb.from("rooms").update(...) sem desestruturar o error e retornavam
ok: true incondicionalmente. O supabase-js devolve { error } em falha do
PostgREST em vez de lançar — então constraint violada, RLS ou timeout viravam
"deu certo" na tela do líder, com a sala parada no lobby e nenhum log.

O cenário concreto da issue: a migração 0004_settings_allowlist.sql aguarda
aplicação pelo dono e adiciona constraints NOT VALID (não validam linhas
legadas, validam todo UPDATE novo). Sala antiga + mudança de linguagem =
UPDATE que falha e UI que confirma o que nunca aconteceu.

O que mudou e por quê

Arquivo Mudança
src/lib/room.ts roomUpdateOutcome({ error, rows }) — decisão pura: error ⇒ 500 + copy neutra · zero linhas confirmadas ⇒ 409 neutro · ≥1 linha ⇒ ok.
src/lib/room.test.ts 5 casos cobrindo os três ramos, a precedência do erro sobre a contagem e o não-vazamento de schema.
route.ts applyRoomUpdate(sb, code, action, patch) — adaptador fino: encadeia .select("code"), loga o detalhe no servidor, devolve NextResponse de erro ou null. Aplicado em settings, start, reset e claim-leader.
route.ts (finish) Passa a reportar o error, preservando flipped.length === 0 = "outro cliente já finalizou" ⇒ segue ok: true sem repersistir (AC 3 não regride).
route.ts (GET) Deixa de devolver error.message do PostgREST ao cliente — useRoom.ts:84 exibe essa string em toast, ou seja, era schema na tela do jogador.

Duas decisões que valem destacar:

  • 409 neutro em vez de "Sala não encontrada". getServerSupabase cai para a
    chave anon quando não há service_role; com RLS, "0 linhas" pode ser SELECT
    negado
    , não sala apagada. A resposta não afirma o que o servidor não sabe.
  • A limpeza de kicked_ids no reset continua best-effort (inerte enquanto a
    0005 não for aplicada); só o reset em si passa a exigir confirmação.

Zero mudança de cliente: postAction já faz if (!json?.ok) fail(...)
toast. As strings novas são copy de produto, no registro das existentes.

Escopo declarado (fora)

Gate (rodado no worktree, resultado real)

Passo Resultado
pnpm install --frozen-lockfile
pnpm typecheck
pnpm build ✅ (14 rotas, sem warning novo)
pnpm test 59 testes / 3 arquivos (era 54 — +5 novos)
node scripts/validate-persistence.mjs ✅ 72/72
validate-metrics.mjs ✅ 37/37
pnpm lint N/A — sem config ESLint no repo; a CI não roda lint

Riscos

Segui o plano do Parecer do Conselho de 2026-07-24 na íntegra (função pura em
room.ts + adaptador na rota, 409 neutro, finish preservado, persistMatch
fora) — a única adaptação foi a renumeração das linhas, que mudaram com o merge
da action kick.

Solicito quórum (HANDBOOK §7)

Closes #56

… a escrita (#56)

As actions `settings`, `start`, `reset` e `claim-leader` faziam `.update()` sem
desestruturar o `error` — e o supabase-js DEVOLVE `{ error }` em vez de lançar.
Constraint violada (a 0004 pendente torna isso provável), RLS ou timeout viravam
"deu certo" na tela do líder, com a sala parada no lobby e nenhum log.

- `roomUpdateOutcome` em `src/lib/room.ts`: decisão pura (erro ⇒ 500, zero linhas
  confirmadas ⇒ 409 neutro, ≥1 linha ⇒ ok), coberta por 5 casos no Vitest.
- `applyRoomUpdate` em `route.ts`: adaptador fino que encadeia `.select("code")`,
  loga o detalhe no servidor e devolve `NextResponse` de erro ou `null`.
- `finish` passa a reportar o `error`, PRESERVANDO `flipped.length === 0` como
  "outro cliente já finalizou" ⇒ segue `ok: true` sem repersistir.
- GET deixa de devolver `error.message` do PostgREST ao cliente (virava toast).

Fora do escopo, declarado na PR: `persistMatch` (colide com a #55) e
`src/app/api/rooms/route.ts`.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
code-racer Ready Ready Preview, Comment Jul 26, 2026 5:45pm

@caioross

Copy link
Copy Markdown
Owner Author

✅ PR Doctor — APROVADA (rooms API / honestidade de escrita)

Diff lido inteiro. Decisão pura roomUpdateOutcome (em room.ts, testada — 5 casos) + helper applyRoomUpdate que só devolve sucesso quando a escrita é confirmada (.select("code") cobre erro do PostgREST e 0 linhas). Aplicado a settings/start/reset/claim-leader.

  • finish tratado corretamente à parte: 0 linhas ali é o caminho feliz ("outro cliente já finalizou") e segue ok:true sem repersistir — só o error, antes descartado, vira falha honesta. ✔️
  • Ganho de segurança: error.message do PostgREST não vai mais para o toast do cliente (useRoom.ts) — detalhe de schema fica no log do servidor, cliente recebe copy neutra. ✔️
  • RLS-aware: 0 linhas → 409 "não foi possível confirmar" (não afirma "sala não encontrada", pois anon+RLS pode negar SELECT). Produção usa service_role.select retorna a linha → ok. ✔️
  • Autorização inalterada (claim-leader segue aberto, escopo Segurança: claim-leader permite sequestro de liderança de qualquer sala (sem autorização) #6/fix(security): exige sala estagnada para claim-leader (anti-sequestro) #12): só passa a reportar falha em vez de fingir sucesso.

CI + Vercel verdes. Closes #56. Squash-merge.

@caioross
caioross merged commit a918b39 into main Jul 26, 2026
3 checks passed
@caioross
caioross deleted the auto/issue-56-honest-room-writes branch July 26, 2026 18:00
caioross added a commit that referenced this pull request Jul 26, 2026
Uniao com origin/main (que trouxe #60/#79/#96/#89). Resolucao:
- room.ts: mantidos os builders (buildMatchRow/buildScoreRows/MatchInsert/
  ScoreInsert) E as funcoes de kick/vote/outcome que entraram na main.
- route.ts: imports unificados; persistMatch segue como wrapper fino dos builders.
- room.test.ts: imports unificados; adicionado kicked_ids:[] ao FULL_ROOM
  (campo virou obrigatorio em RoomRow via #79).
Gate: typecheck OK, build OK, test 103 OK, validate-persistence 72/0.
caioross added a commit that referenced this pull request Jul 26, 2026
Uniao com origin/main (#55/#60/#79/#89/#96). Resolucao (tudo aditivo):
- useRoom.ts: mantidos isSpectatorJoin + isSpectator E as importacoes/logica de
  votacao (#89, tallyVotes/pickVoteWinner/activeVotes/voteTally/myVote).
- RoomView.tsx: mantido o branch isSpectator?SpectatorView:Race E os props
  isLeader/onKick do Race (#79), alem de voteTally/myVote no destructure.
- room.test.ts: mantidos os blocos de isSpectatorJoin (#64) e votacao/outcome.
Gate: typecheck OK, test 109 OK, build OK, validate-persistence 72/0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confiabilidade/API: 4 das 5 actions de sala descartam o error do .update() e respondem ok:true em silêncio (rooms/[code]/route.ts)

1 participant