0035 — Autorização: fechar os portões, sanear as permissões, e o menu obedecer a elas¶
| Status | CONCLUÍDA |
| Branch | spec/0035-menu-por-permissao |
| Criada em | 2026-08-11 |
| Apps afetados | api · web · e2e |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDAA spec cresceu durante a análise, e o motivo importa. Ela nasceu como "ajustar o menu de acordo com as permissões". Ao levantar a matriz para escrever o mapa do menu, a auditoria encontrou 13 endpoints sem verificação de permissão, e num deles uma escalada de privilégio explorável em produção. Ajustar a vitrine antes da fechadura seria inverter a ordem: o menu é conveniência, a autorização é o que segura. O escopo passou a ser o modelo de autorização inteiro, e o menu é o último item dele.
1. Problema¶
1.0 Qualquer usuário do tenant vira administrador dele¶
POST /api/v1/onboarding/users exige apenas estar autenticado, e OnboardingService.invite_team_user grava o papel que veio no corpo da requisição, sem olhar quem pediu:
A cadeia inteira: o solicitante escolhe um e-mail que controla, pede papel ADMIN_TENANT, recebe o link de convite, define a senha e entra como administrador da garantidora. Nenhuma etapa exige nada além de uma sessão válida.
Verificado, não deduzido. No dev-remote, rival@e2e.test — um AGENCY_USER, isto é, alguém de uma imobiliária parceira, externo à empresa — convidou um ADMIN_TENANT e recebeu HTTP 201. O usuário criado no teste foi removido.
Pior: data.role não é validado contra lista nenhuma, então ADMIN_GLOBAL também passa — o que transforma administrador de uma garantidora em administrador da plataforma.
O mesmo desenho aparece em POST /onboarding/partner-agencies/{id}/invite: qualquer autenticado convida usuário para qualquer imobiliária do tenant, inclusive a concorrente.
1.1 Treze endpoints sem porteiro¶
| Endpoint | Situação |
|---|---|
POST /onboarding/users | escalada de privilégio (1.0) |
POST /onboarding/partner-agencies/{id}/invite | idem, escopado a qualquer imobiliária |
POST /onboarding/partner-agencies | qualquer autenticado cria imobiliária |
PATCH /onboarding/tenant | qualquer autenticado altera dados da garantidora |
POST /onboarding/activate | qualquer autenticado ativa o ambiente |
DELETE /onboarding/ | qualquer autenticado cancela o onboarding |
POST /tenants, POST/DELETE /tenants/{id}/purge | cobertos pelo serviço (_require_admin_global) |
POST /auth/register, GET /onboarding/summary, GET /onboarding/tenant, GET /tenant-invites/validate | corretos por desenho |
1.2 Cinco permissões que não governam nada¶
São 59 definidas e 52 exigidas por algum endpoint. As cinco restantes existem no papel de alguém e não têm efeito: credit_analyses:approve, credit_analyses:reject, credit_analyses:delete, decisions:list e partner_agencies:create.
As duas primeiras são as mais enganosas: a matriz sugere que aprovar e reprovar crédito são permissões próprias, quando na prática quem governa as duas é decisions:update. Uma permissão declarada e não aplicada é pior que ausente — ela responde "sim" à pergunta "isto está protegido?" sem proteger.
1.3 O menu é uma lista fixa¶
buildNavGroups (components/navigation/nav-groups.tsx) devolve os mesmos 14 itens para todo usuário autenticado do tenant, sem consultar permissão nenhuma. A sider do desktop e a gaveta do mobile consomem essa lista, então o defeito aparece nos dois.
Quem sente primeiro é a imobiliária parceira. O usuário dela abre o app e vê, no grupo ADMINISTRAÇÃO, Minha Garantidora, Equipe, Perfis de Acesso, Parâmetros, Tipos de Documento, Documentos da Análise e Auditoria — a estrutura administrativa da garantidora, oferecida a quem é de fora dela. Clicar não vaza dado (a API responde 403), mas o menu já revela como a empresa se organiza, e todo item é um convite a um beco sem saída.
Não é só a imobiliária. O AGENCY_USER também vê Garantias e Imobiliárias, que ele não pode listar; o OPERATOR vê Equipe, Perfis de Acesso e Auditoria; o VIEWER vê tudo isso e ainda Parâmetros. Em nenhum caso o menu diz a verdade sobre o que a pessoa pode fazer.
E esconder não bastaria. A rota continua registrada: quem digitar /audit-logs na barra de endereços chega à tela, que monta e só então falha com 403 da API — quando falha. Uma tela que só sabe carregar dado proibido fica em esqueleto, como aconteceu com o Centro de Operações na jornada da imobiliária (spec 0034).
O Painel virou privilégio de administrador sem que se decidisse isso¶
O Centro de Operações se alimenta do log de auditoria e o backend o fecha com audit:list (routers/operations_center.py:23), permissão que só ADMIN_GLOBAL e ADMIN_TENANT possuem. Enquanto o HomeGuard entregava a tela a todos, o efeito ficou escondido: analista, operador e visualizador recebiam a página, ela falhava por baixo e mostrava esqueleto.
A correção da spec 0034 passou a redirecionar quem não tem a permissão para /credit-analyses. Isso consertou a imobiliária — e, no mesmo movimento, tirou o Painel de analista, operador e visualizador, que é uma decisão de produto que ninguém tomou. Está no ar desde 2026-08-11.
2. Objetivo¶
Que a navegação diga a verdade: cada pessoa vê no menu exatamente o que pode usar, nos dois viewports, e a rota correspondente recusa quem não deveria chegar nela — com uma tela que explica, não com um erro cru.
3. Fora de escopo¶
- Os 10 papéis area-scoped de
docs/navigation-brief.md. Esta spec faz o menu obedecer à matriz de permissões que existe; redesenhar a matriz é outra feature. - Reescrever a matriz de permissões dos papéis atuais. A única permissão criada aqui é a do Painel (RN3), porque sem ela não há como responder à pergunta que a spec levanta. Nada mais é concedido nem retirado.
- Menu do SaaS admin (
buildAdminNavGroups). Aquela área é exclusiva deADMIN_GLOBAL, que tem todas as permissões — filtrar lá não muda nada. - Autorização no backend. Já existe e é a que vale; o menu é conveniência de interface. Esta spec não afrouxa nem endurece nenhum
require_permission, com a única exceção do Painel (RN3). - Itens "Em breve" (Cobranças, Sinistros) deixarem de ser placeholders. Continuam desabilitados; ganham só o recorte por permissão (RN6).
4. Regras de negócio¶
Autorização — todo endpoint tem porteiro¶
- RN0. Nenhum endpoint autenticado fica sem verificação. Ou exige permissão (
require_permission/require_any_permission), ou exige papel de sistema (require_system_role), ou está numa lista de exceção nomeada e justificada, coberta por teste. Estar autenticado prova quem você é, não o que você pode. - RN0a. As exceções legítimas, e por quê:
POST /auth/registereGET /tenant-invites/{token}/validate— públicos por natureza; a credencial é o próprio token.POST /onboarding/tenant— o usuário acabou de se registrar e ainda não tem tenant nem papel nenhum; exigir permissão aqui tornaria o cadastro impossível. É ele que cria o tenant e recebeADMIN_TENANT.GET /onboarding/summaryeGET /onboarding/tenant— leem o próprio tenant do solicitante, sem parâmetro que permita apontar para outro.- RN0b. Convite não concede o que quem convida não tem. O papel pedido no corpo é validado contra uma lista fechada, e
ADMIN_GLOBALnunca é concedível por convite — administrador de plataforma se cria por outro caminho. Papel desconhecido responde 422;ADMIN_GLOBALresponde 403. - RN0c. Toda permissão definida em
_PERMISSIONSé exigida por pelo menos um endpoint. Permissão que não governa nada é removida; a que deveria governar passa a governar. Coberto por teste, para não voltar a divergir.
O princípio¶
- RN1. Todo item de navegação declara a permissão que o habilita. Sem a permissão, o item não é renderizado — não aparece desabilitado, não aparece esmaecido, some. Menu que mostra o que não se pode usar ensina o usuário a tentar e falhar, e revela a estrutura interna a quem está de fora.
- RN2. Navegar pede a permissão de leitura; administrar pede a de alteração. Um item que leva a uma tela de consulta é habilitado pelo
:listdo recurso. Um item que leva a uma tela de parametrização é habilitado pela permissão que permite mudá-la, não pela de lê-la. É o que separa "preciso do nome do tipo de documento para montar um checklist" de "administro os tipos de documento da garantidora" — oAGENCY_USERtem a primeira necessidade e nenhuma das duas telas.
O Painel¶
- RN3. Nasce a permissão
operations_center:read, concedida aADMIN_TENANT,ANALYST,OPERATOReVIEWER(e aADMIN_GLOBAL, que tem todas). Os endpoints de/operations-centerpassam a exigi-la no lugar deaudit:list. - RN4. O painel deixa de depender de
audit:listporque auditoria sai do painel. O bloco "Atividade recente" lia a tabelaaudit_logsdireto — era o log com outro nome —, e enquanto ele morava ali não havia resposta boa: ou o painel inteiro exigia permissão de auditoria e sumia para quem opera, que é o público da tela, ou o log vazava para quem não pode lê-lo. Auditoria fica na área administrativa, na tela de Auditoria, que já existe e é fechada poraudit:list.
Some, portanto: o endpoint GET /operations-center/activity-feed, o método get_activity_feed, os schemas ActivityItem/ActivityFeedResponse e o bloco na interface. Sobram seis endpoints no painel, nenhum lendo auditoria — que é o que torna correto conceder operations_center:read a quem opera.
Esta regra nasceu errada e foi corrigida depois de a implementação já estar feita. A primeira versão dizia que amarrar painel e auditoria "foi atalho de implementação" — e isso era falso para um dos sete endpoints, justamente o que lia o log. Trocar o portão de todos por
operations_center:readabriu o feed de auditoria paraANALYST,OPERATOReVIEWER: fechou uma porta e abriu a janela ao lado. A pergunta do usuário — "por que o audit:list é necessário no painel?" — é o que expôs isso. - RN5. OHomeGuardpassa a testaroperations_center:read. Quem não a tem continua indo para/credit-analyses— hoje, só a imobiliária parceira.
O mapa¶
- RN6. A tabela abaixo é normativa. Cada item existe no menu se e somente se o usuário tem a permissão da coluna.
| Item | Grupo | Habilitado por |
|---|---|---|
| Painel | — | operations_center:read |
| Análise de Crédito | OPERAÇÃO | credit_analyses:list |
| Garantias | OPERAÇÃO | guarantees:list |
| Cobranças (em breve) | OPERAÇÃO | billings:list |
| Sinistros (em breve) | OPERAÇÃO | guarantees:list |
| Inquilinos | CADASTROS | renters:list |
| Imóveis | CADASTROS | properties:list |
| Imobiliárias | CADASTROS | partner_agencies:list |
| Minha Garantidora | ADMINISTRAÇÃO | tenants:read |
| Equipe | ADMINISTRAÇÃO | users:list |
| Perfis de Acesso | ADMINISTRAÇÃO | roles:list |
| Parâmetros | ADMINISTRAÇÃO | papel de sistema ADMIN_TENANT |
| Tipos de Documento | ADMINISTRAÇÃO | document_types:create |
| Documentos da Análise | ADMINISTRAÇÃO | credit_analysis_document_requirements:update |
| Auditoria | ADMINISTRAÇÃO | audit:list |
Três escolhas dentro da tabela merecem o porquê:
- Sinistros herda
guarantees:listporque o sinistro nasce de uma garantia; quem não enxerga a garantia não terá o que acionar. - Parâmetros é a única linha gated por papel, e não por permissão, porque a API também é (
require_system_role("ADMIN_TENANT")emrouters/tenant_settings.py). O menu espelha o backend; inventar uma permissão que o backend ignora faria o item aparecer para quem tomaria 403. -
Equipe fica em
users:list, e não na permissão de alteração como manda a RN2, porque a tela é a lista do time — consulta, com ações para quem pode. A matriz concedeusers:listaANALYSTeVIEWERdeliberadamente, e escondê-la deles retiraria um acesso que hoje existe e ninguém pediu para tirar. -
RN7. Grupo cujos itens sumiram todos não renderiza o rótulo da seção. Um "ADMINISTRAÇÃO" seguido de nada é pior que a ausência: parece defeito de carregamento.
- RN8. As abas da barra inferior do mobile (
buildNavTabs) seguem a mesma tabela. A barra se recompõe com as abas que sobrarem; a aba "Mais", que abre a gaveta, nunca some — é o acesso ao resto do menu.
As permissões saneadas¶
- RN11. Os endpoints de onboarding passam a exigir:
| Endpoint | Passa a exigir | Por quê |
|---|---|---|
POST /onboarding/users | users:create + RN0b | criar usuário é criar usuário, esteja no onboarding ou não |
POST /onboarding/partner-agencies/{id}/invite | users:create + RN0b | idem — o convite cria conta |
POST /onboarding/partner-agencies | partner_agencies:create | dá uso à permissão que existia sem dono (1.2) |
PATCH /onboarding/tenant | tenants:update | é a mesma alteração que PATCH /tenants/{id} faz |
POST /onboarding/activate | papel ADMIN_TENANT | ativar o ambiente é ato do dono da garantidora |
DELETE /onboarding/ | papel ADMIN_TENANT | cancelar o onboarding apaga o tenant |
users:create está hoje só com ADMIN_TENANT e ADMIN_GLOBAL — exigi-la já fecha a escalada da seção 1.0 para os outros quatro papéis, e a RN0b fecha o degrau que resta (o ADMIN_TENANT fabricando um ADMIN_GLOBAL).
- RN12.
credit_analyses:approveecredit_analyses:rejectpassam a valer: os endpoints/decision/approvee/decision/rejectpassam a exigi-las, além dedecisions:update. Decidir crédito é a operação de maior consequência do produto e merece permissão própria — que já existia na matriz e não fazia nada. Os papéis que as têm hoje (ADMIN_TENANT,ADMIN_GLOBAL) e oANALYST, que precisa delas para trabalhar, as recebem. - RN13.
credit_analyses:deleteedecisions:listsão removidas: não há endpoint que as justifique, e inventar um só para dar emprego a uma permissão seria o rabo abanando o cachorro. Se a exclusão de análise vier a existir, a permissão volta com ela.
As rotas¶
- RN9. Toda rota correspondente a um item da tabela exige a mesma permissão. Acessada sem ela — por URL digitada, link antigo ou favorito —, a aplicação mostra a tela de acesso negado que já existe (
pages/unauthorized), sem montar a tela nem disparar a requisição que falharia. - RN10. A verificação acontece depois de a identidade carregar. Enquanto
useCurrentUserestá em voo, a rota mostra carregamento — nunca acesso negado, que seria uma acusação falsa e piscaria na tela a cada navegação.
5. Impacto por app¶
apps/api¶
app/db/rbac_seed.py— nova permissãooperations_center:readem_PERMISSIONS, concedida aADMIN_TENANT,ANALYST,OPERATOReVIEWERem_ROLE_PERMISSIONS.app/api/v1/routers/operations_center.py—_REQUIRE_AUDITvira_REQUIRE_PAINEL = require_permission("operations_center", "read"), aplicado aos seis endpoints do painel.- Migration: não há. Permissões e vínculos vivem em tabela e são semeados pelo
seed_rbac, que é idempotente (ON CONFLICT DO NOTHING) e roda pormake seed. O papel de sistema é global (tenant_id IS NULL), então uma execução atende todos os tenants. Consequência para o deploy: não bastamake migrate— produção precisa demake seedpara o painel voltar a analista, operador e visualizador. - Testes:
tests/test_rbac_security.pyganha a nova permissão na matriz esperada; teste novo garantindo queAGENCY_USERcontinua recusado no painel e que os outros quatro papéis passam. - Swagger: as descrições dos endpoints de
/operations-centerpassam a citaroperations_center:read, e a seção de papéis emmain.pyganha a permissão nova.
apps/web¶
components/navigation/nav-groups.tsx—NavItemganha o campo que declara a exigência (permissão ou papel de sistema).buildNavGroupsebuildNavTabspassam a receber as permissões do usuário e devolvem só o que ele pode ver, descartando grupos que ficaram vazios (RN7).components/custom-sider/index.tsxecomponents/mobile-shell/index.tsx— passam as permissões para os construtores. Nenhuma lógica de permissão dentro dos componentes: quem decide é o construtor, que é a fonte única (RN6).App.tsx— um guarda de rota por permissão, aplicado às rotas da tabela;HomeGuardpassa a testaroperations_center:read(RN5).shared/utils/roleUtils.ts— o teste de papel de sistema, para a linha Parâmetros.data-testidnovos:nav-hidden-<recurso>não existe (item ausente não tem nó); a asserção do e2e é sobre a ausência donav-<path>que a sider já gera. Entramunauthorized-pagena tela de acesso negado enav-section-<slug>nos rótulos de seção, para o e2e afirmar a RN7.- Responsividade & PWA: a RN8 é especificamente de mobile — a barra inferior muda de composição por papel e precisa continuar legível com 1, 2 ou 3 abas. PWA: só muda bundle; a tela instalada reflete após reabrir.
apps/e2e¶
- Specs:
menu-por-permissao.spec.ts— paraimob(parceira),analista,financeiro(OPERATOR) egestor(ADMIN_TENANT), afirma quais itens aparecem e quais não aparecem, nos dois viewports; e que/audit-logsdigitado por quem não pode mostra a tela de acesso negado. - Seeds: sem dados novos — os papéis do seed atual já cobrem os quatro casos.
6. Contrato de API¶
Nenhuma rota nova, nenhuma removida. Muda a permissão exigida pelo painel:
| Antes | Depois |
|---|---|
audit:list | operations_center:read |
Vale para overview, alerts, work-queue, activity-feed, filters e quick-actions. Quem não tem a permissão recebe 403 (FORBIDDEN) — é falta de permissão no próprio tenant, não acesso cruzado, então não é 404 (spec 0034, RN4).
7. Critérios de aceite¶
- [x] CA1. Dado o usuário da imobiliária autenticado, quando abro o app em desktop e em mobile, então o menu não contém Minha Garantidora, Equipe, Perfis de Acesso, Parâmetros, Tipos de Documento, Documentos da Análise, Auditoria, Garantias nem Imobiliárias — e o rótulo ADMINISTRAÇÃO também não aparece (RN7).
- [x] CA2. Dado o mesmo usuário, quando abro o menu, então vejo Análise de Crédito, Inquilinos e Imóveis.
- [x] CA3. Dado um
ADMIN_TENANT, quando abro o menu, então vejo os 15 itens da tabela da RN6 — nenhum sumiu para quem pode tudo no tenant. - [x] CA4. Dado um
OPERATOR, quando abro o menu, então vejo Tipos de Documento (temdocument_types:create) e não vejo Documentos da Análise, Perfis de Acesso, Minha Garantidora nem Auditoria. - [x] CA5. Dado um
ANALYST, quando abro o menu, então vejo Painel e Equipe, e não vejo Tipos de Documento. - [x] CA6. Dado um
ANALYST,OPERATORouVIEWER, quando entro no app, então caio no Painel e ele carrega com dados — não em/credit-analyses, e não em esqueleto. - [x] CA7. Dado o usuário da imobiliária, quando entro no app, então caio em
/credit-analyses, e nenhuma requisição a/operations-center/*é disparada. - [ ] CA8. Dado o usuário da imobiliária, quando digito
/audit-logsna barra de endereços, então vejo a tela de acesso negado e nenhuma requisição à API de auditoria é disparada. - [ ] CA9. Dado qualquer papel, quando a identidade ainda está carregando, então a rota mostra carregamento e nunca pisca a tela de acesso negado (RN10).
- [x] CA10. Dado o usuário da imobiliária no mobile, quando olho a barra inferior, então ela mostra as abas que sobraram mais "Mais", sem espaço vazio nem sobreposição.
- [x] CA11. Dado
make test, quando rodo a suíte, então há teste afirmando queAGENCY_USERrecebe 403 no painel e que os outros quatro papéis recebem 200. - [ ] CA12. Dada a jornada da imobiliária recapturada, quando abro
docs/journeys/index.html, então os prints não mostram nenhum item de ADMINISTRAÇÃO no menu do parceiro.
8. Plano de implementação¶
Ordem obrigatória no monorepo: api → web → e2e.
- [ ]
apps/api— permissãooperations_center:readnorbac_seede nos quatro papéis - [ ]
apps/api— endpoints do painel exigindo a permissão nova - [ ]
apps/api— testes (matriz de permissões + painel por papel) - [ ]
apps/api— Swagger dos endpoints do painel e a seção de papéis emmain.py - [ ]
apps/api— rodarseed_rbacno dev-remote e conferir no banco - [ ]
apps/web—NavItemdeclara a exigência; construtores filtram e descartam grupo vazio - [ ]
apps/web— sider e casca mobile passando as permissões - [ ]
apps/web— guarda de rota por permissão +HomeGuardna permissão nova - [ ]
apps/web— responsivo (desktop e mobile), com a barra inferior recomposta - [ ]
apps/e2e—menu-por-permissao.spec.tscobrindo os quatro papéis - [ ]
make testemake lintpassando (sem apontamento novo nos arquivos tocados) - [ ] Validado no browser em desktop e mobile, com os quatro papéis
- [ ] Jornada da imobiliária recapturada (CA12)
9. Decisões em aberto¶
Nenhuma. As quatro que existiam foram fechadas em 2026-08-11:
- Painel para quem opera — permissão própria (
operations_center:read), em vez de emprestaraudit:list. O painel volta a analista, operador e visualizador sem ninguém ganhar acesso ao log de auditoria. - Telas de parametrização — habilitadas pela permissão de alteração, não pela de leitura. Virou a RN2 e vale para além do caso que a originou.
- Item sem permissão — some do menu.
- Alcance — menu e rotas protegidas. Esconder sem barrar seria tirar a placa e deixar a porta destrancada.
10. Registro¶
O que divergiu do planejado¶
Dois endpoints voltaram atrás, e é correção de erro meu. POST /onboarding/activate e DELETE /onboarding receberam require_system_role("ADMIN_TENANT") na primeira passada. A suíte mostrou o preço: as duas operações agem apenas sobre o próprio solicitante, e o cancelamento é o caminho pelo qual alguém que se registrou e nunca criou garantidora apaga a própria conta. Essa pessoa não tem papel nenhum — o gate a trancaria para sempre. Voltaram a ser só-autenticado, agora com justificativa registrada na lista de exceções (RN0a), que é o que diferencia "esquecemos" de "decidimos".
O seed precisou aprender a remover. Não estava previsto. seed_rbac só inseria (ON CONFLICT DO NOTHING), então extinguir credit_analyses:delete e decisions:list do código não as tirava de ambiente nenhum: seguiam concedidas em 2 e 4 papéis no dev-remote. Sem reconciliação, metade desta spec não teria efeito onde já está instalada. Agora o seed retira do papel o que a declaração não lista e apaga a permissão órfã — só para papéis de sistema; perfis customizados por tenant são do cliente.
require_all_permissions é nova. A RN12 pede que aprovar crédito exija decisions:update e credit_analyses:approve, e só existiam a conjunção implícita (uma permissão) e a disjunção (require_any_permission).
Os 27 testes que quebraram¶
Nenhum era regressão de produto. Todos passavam por ausência de porteiro, e é isso que os torna interessantes:
test_credit_analysesetest_documentsmontavam o mundo inteiro com umOPERATOR, inclusive cadastrando imobiliária parceira — que a matriz nunca deu a esse papel. O endpoint não exigia nada, então o teste "passava".test_onboardingetest_onboarding_cancelpercorriam o fluxo sem semear os papéis de sistema, e o usuário terminava o onboarding sem papel algum. Em produção os papéis sempre existem; o ambiente de teste é que era irreal.test_operations_centermantinha uma lista de permissões escrita à mão — o espelho manual que oconftestjá havia abandonado por divergir. Ela citavadecisions:list, extinta, e não tinhaoperations_center:read. Passou a derivar de_ROLE_PERMISSIONS.test_overview_requires_admin_tenantafirmava que oVIEWERnão podia ver o painel. Afirmava um acidente: o painel exigiaaudit:listporque foi o que estava à mão. Viroutest_painel_liberado_a_quem_opera, com um par novo (test_painel_negado_a_imobiliaria) para a fronteira que de fato importa.
Aferições¶
make test: 287 testes (eram 279; os 8 novos são a suíte de autorização e o par do painel). Duas execuções completas: uma com 287 passando e 0 falhas, outra com 286 e 1 falha — a intermitente de paginação já documentada naCLAUDE.mdda raiz, sem relação com esta spec.- Barreira exercitada, não só escrita: removi o
require_permission("users","create")do convite dentro do container e a suíte acusou pelos dois caminhos — a introspecção (test_todo_endpoint_autenticado_tem_porteiro) e o comportamento (test_convite_exige_permissao_de_criar_usuario). - Banco reconciliado no dev-remote: 58 permissões, zero extintas,
audit:listsó com os dois administradores eoperations_center:readcom os quatro papéis que operam. tscdo web: limpo.- Menu verificado no browser, quatro papéis × dois viewports, contra o dev-remote reconstruído:
| Papel | Itens | Confere |
|---|---|---|
imob (imobiliária) | 3 — Análise, Inquilinos, Imóveis | sem seção ADMINISTRAÇÃO (RN7) |
analista | 7, com Painel e Equipe | sem parametrização nem Auditoria |
financeiro (OPERATOR) | 7, com Tipos de Documento | sem Equipe nem Documentos da Análise |
gestor (ADMIN_TENANT) | 13 — todos | — |
Sem rolagem horizontal em nenhum. No mobile a barra inferior da imobiliária fica com duas abas (Análise + "Mais"), e a aba "Mais" sobrevive ao recorte, como a RN8 exige.
Uma armadilha de verificação, que vale registrar. A primeira checagem casava os itens pelo rótulo visível e devolveu "nenhum item" no desktop para os quatro papéis, com o menu perfeitamente renderizado: a casca mobile fica no DOM junto com a sider, oculta, e getByText(...).first() pegava o nó errado. O spec e2e foi escrito com os data-testid nav-<path>/mobile-nav-<path> por causa disso — teste que mede o nó errado é pior que teste ausente, porque dá confiança.
Pendências¶
- e2e escrito, não executado (
menu-por-permissao.spec.ts). - Produção precisa de
make seedalém do deploy: a permissão nova e a reconciliação são dado, não schema. Sem isso o painel não volta a analista, operador e visualizador — e as permissões extintas continuam concedidas lá.