Skip to content

Inverte a validação de papel para fail-closed - #91

Merged
Hirley merged 1 commit into
mainfrom
claude-hirley/papel-fail-closed
Sep 1, 2026
Merged

Hirley merged 1 commit into
mainfrom
claude-hirley/papel-fail-closed

Conversation

@Hirley

@Hirley Hirley commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fecha a #79.

User#validar_atribuicao_de_papel começava com return if ator.blank?. A regra que impede um líder de virar admin só rodava se alguém tivesse preenchido o ator — um attr_accessor que não é coluna.

Os dois controllers de hoje preenchem, e há specs cobrindo isso. Mas a invariante valia por disciplina, não por construção: um job, um importador, uma rake task, um endpoint futuro, um update no console — qualquer caminho novo que esquecesse passava direto e em silêncio. Como a issue coloca, o problema nunca foi a decisão de ter um ator; foi o default escolhido para o caso não previsto.

Agora ator ausente é recusa. Os poucos lugares legítimos que escrevem papel fora de uma requisição declaram isso com ator_dispensado: db/seeds.rb, o console e a factory da suíte.

O nome é incômodo de propósito

ator_dispensado não deve ser o caminho fácil. Quem o usa está afirmando "não há requisição aqui, não há fronteira a defender" — isso precisa ser uma decisão consciente, não um atalho para calar uma validação que apareceu no caminho. Está comentado assim no model.

Duas decisões

A criação também é coberta, não só a alteração. Um caminho que crie um usuário já com role=admin sem ator é tão perigoso quanto um que promova alguém depois.

Na factory, a dispensa vale só durante a criação — um after(:create) limpa a marca. Sem essa limpeza os specs de spec/models/user_spec.rb passariam por vacuidade, já que todos partem de um usuário criado ali. Um spec que mude o papel depois está exercitando a regra e informa o ator, como qualquer requisição informaria.

O que não fiz

Não criei um Users::ChangeRole. A issue já antecipava que ele não resolve sozinho, e concordo: enquanto user.update(role:) continuar acessível, é o model que precisa preservar a invariante — e é o model que a preserva agora. Um caso de uso por cima seria conveniência, não garantia.

Verificação

No container: RuboCop 110 arquivos / 0 ofensas, zeitwerk:check All is good!, RSpec 391 exemplos / 0 falhas (eram 388).

Vale registrar que nenhum spec fora do arquivo desta regra precisou de ajuste — era o risco real da mudança, e o after(:create) na factory foi o que o absorveu. O spec que antes afirmava "não roda sem ator (seeds e console seguem funcionando)" foi invertido para afirmar a recusa, com um caso novo cobrindo a saída declarada.

Nada de JS, CSS, HAML ou CSP mudou.

User#validar_atribuicao_de_papel começava com `return if ator.blank?`:
a regra que impede um líder de virar admin só rodava se alguém tivesse
preenchido o ator, um attr_accessor que não é coluna.

Os dois controllers de hoje preenchem, e há specs cobrindo isso — mas a
invariante valia por disciplina, não por construção. Um job, um
importador, uma rake task, um endpoint futuro, um update no console:
qualquer caminho novo que esquecesse passava direto e em silêncio. O
default escolhido para o caso não previsto era o errado.

Agora ator ausente é RECUSA. Os poucos lugares legítimos que escrevem
papel fora de uma requisição declaram isso com ator_dispensado:
db/seeds.rb, o console e a factory da suíte. O custo recai sobre eles,
que é onde ele deve cair — e o esquecimento vira erro visível em vez de
brecha.

O nome ator_dispensado é incômodo de escrever de propósito. Ele não deve
ser o caminho fácil: quem o usa está afirmando "não há requisição aqui,
não há fronteira a defender", e isso precisa ser consciente, não um
atalho para calar uma validação que apareceu no caminho.

Duas decisões que valem registro:

A criação também é coberta, não só a alteração. Um caminho que crie um
usuário já com role=admin sem ator é tão perigoso quanto um que promova
alguém depois.

Na factory, ator_dispensado vale só durante a criação — um after(:create)
limpa a marca. Sem essa limpeza os specs de spec/models/user_spec.rb
passariam por vacuidade, já que todos partem de um usuário criado por
ali. Um spec que mude o papel DEPOIS está exercitando a regra e informa
o ator, como qualquer requisição informaria.

Não criei um caso de uso Users::ChangeRole. Ele não resolveria sozinho:
enquanto user.update(role:) continuar acessível, é o model que precisa
preservar a invariante — e é o model que a preserva agora.

Verificado no container: RuboCop 110 arquivos sem ofensas,
zeitwerk:check limpo, RSpec 391 exemplos e 0 falhas (eram 388), sem
nenhum ajuste em spec fora do arquivo desta regra.

Closes #79

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hirley
Hirley merged commit 5f62e2b into main Sep 1, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in @Hirley's task_keeper_api Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant