# Gestão de Permissões — Aproveitamento no V7

> Julgamento de portabilidade frente ao V7 (Laravel 12/PHP 8.4) e às ADRs.
> Cada decisão cita a regra que a sustenta. O V7 é **deny-by-default**, com
> vocabulário de quatro ações e sem bypass de identidade.

## 1. Veredito por artefato

| Artefato legado | Veredito | Destino no V7 |
|---|---|---|
| Tela de matriz de permissões (UX) | **Portar como referência de UX** | componente `<x-permission-matrix>` com checkboxes por ação |
| Lista de perfis com contagem de usuários | **Portar (adaptando)** | `cms::roles.index` com paginação e busca server-side |
| Busca/ordenação/paginação das listas | **Portar (adaptando)** | componentes nativos, sem JS inline |
| Um perfil por usuário | **Portar (conceito)** | `cms_users.role_id` (ADR-033, decisão 3) |
| Auditoria de mudança de perfil/usuário | **Portar (conceito)** | `AuditWriter`/subscriber (ADR-035) |
| Bloqueio de módulos restritos na tela | **Portar (conceito)** | filtro pelo manifesto; guard de rota (ADR-033/043) |
| Proteção do perfil reservado de suporte | **Portar (conceito, endurecido)** | regra de código, não de tela (ADR-033, decisão 4) |
| Resolução tolerante de chaves (aliases + normalização) | **Adaptar** | usar apenas como script de conversão de dados; o runtime usa token canônico |
| Níveis `none/read/write` | **Adaptar** | mapear para `ver`/`editar` (+ `aprovar`/`arquivar` quando declarados) |
| `active_pattern` | **Adaptar** | prefixo de rota do módulo declarado no manifesto + guard |
| `V2Role` + `permissions_v2` (mapa jsonb) | **Rejeitar** | `core_roles.permissoes` como **lista** de tokens (ADR-033) |
| `PermissionService` | **Rejeitar** | `Gate` nativo + helper `Core\Access\Permission` (ADR-033) |
| `V2PermissionMiddleware` | **Rejeitar** | autorização por ação no módulo; middleware só de ativação (ADR-034) |
| Bypass por e-mail fixo | **Rejeitar** | perfil reservado `suporte`, sem endereço mágico (ADR-033) |
| Sentinel V1 convivendo com RBAC V2 | **Rejeitar** | sessão nativa sobre `cms_users` (ADR-037) |
| Soft delete de perfis | **Rejeitar** | roles como dado; exclusão explícita e auditada (ADR-017/041) |
| `users.permissions_v2` | **Rejeitar** | descartar após conferência de dados |
| Script inline de slug e `confirm()` | **Rejeitar** | JS mínimo e componente acessível, CSP sem inline (ADR-010/022/025) |
| `@v2can` | **Rejeitar** | `@can`/`Gate` nativo |

## 2. O que portar — padrão concreto

### 2.1 Matriz de permissões como componente

O valor real da tela legada é a **matriz módulo × nível**, com grupo, ícone e
três estados. No V7 ela vira uma matriz **módulo × ação**, alimentada pelo
manifesto:

```text
Módulo            Grupo           ver   editar   aprovar   arquivar
Notícias          Publicações     [x]   [x]      [ ]       [ ]
Licitações        Atos Oficiais   [x]   [ ]      [ ]       [ ]
```

Regras de UX que valem manter:

- agrupar por grupo do manifesto;
- mostrar o estado atual sem esconder ações não suportadas (desabilitar, não
  omitir);
- busca por nome de módulo;
- resumo do tipo “3 módulos com edição”;
- confirmação antes de remover acesso.

O componente recebe um DTO do manifesto; **não** lê `v2.modules.configs` nem
chama serviço de permissão.

### 2.2 Um perfil por usuário

O ADR-033, decisão 3, mantém **um perfil por usuário** (`cms_users.role_id`,
FK `nullOnDelete`). É exatamente o conceito legado, com identificador nativo
em vez de UUID.

### 2.3 Módulos restritos invisíveis

O legado remove módulos `restricted` da matriz e esconde o grupo `Support` da
sidebar. O V7 mantém a regra, mas **no servidor**:

- manifesto não lista o grupo `Suporte` para quem não é da equipe;
- guard de rota responde 404, não 403 (ADR-033, decisão 5; ADR-043);
- o perfil de maior privilégio **não** contorna.

### 2.4 Auditoria de mudanças

Cada alteração de `core_roles.permissoes` e de `cms_users.role_id` deve gerar
evento de auditoria com ator, alvo, antes e depois (ADR-035, decisões 3 e 7).
No legado, o `V2AuditLogger` já registra `created/updated/deleted`; a diferença
é que no V7 a escrita é centralizada (`AuditWriter`) e o `before/after` é
obrigatório.

### 2.5 Exclusão protegida

O legado recusa excluir perfil com usuários vinculados
(`PermissionService::deleteRole`, `:344-378`) e desabilita o botão na UI. O V7
mantém a regra no servidor e adiciona: perfil reservado nunca é excluído,
mesmo sem usuários.

## 3. O que adaptar

### 3.1 Níveis → ações

| Legado | V7 | Observação |
|---|---|---|
| `none` | nenhum token | ausência = negação |
| `read` | `{Modulo}.ver` | nunca implica editar |
| `write` | `{Modulo}.ver` + `{Modulo}.editar` | somar `aprovar`/`arquivar` só se o módulo declarar |

O ADR-033, decisão 1, fixa quatro ações: `ver`, `editar`, `aprovar`,
`arquivar`. Não existe `write` genérico. A conversão de `write` para as quatro
ações indiscriminadamente **amplia privilégio** e deve ser rejeitada.

### 3.2 `active_pattern` → prefixo de rota

O legado casa URL com glob e infere o módulo. No V7:

- o módulo declara seu prefixo no manifesto (ADR-014);
- o guard de ativação usa o nome canônico (ADR-034);
- a autorização usa o token do módulo, não a URL.

Isso elimina a possibilidade de “rota fora do mapa” (R-02) e a ambiguidade de
dois padrões que casam com o mesmo path.

### 3.3 Resolução tolerante → conversão de dados

Aliases e normalização existem para ler perfis antigos. No V7, essa lógica
**não pertence ao runtime**:

- o script de migração resolve cada chave legada para o token canônico;
- tokens inválidos são reportados, não silenciados;
- o runtime compara strings exatas.

Vantagem: o comportamento fica previsível e não depende de uma lista
hardcoded que precisa ser lembrada a cada módulo novo.

### 3.4 Suporte → perfil reservado

| Legado | V7 |
|---|---|
| `config('v2.support.email')` em três pontos | conta reservada + perfil `suporte` |
| bypass em `v2_can()` e middleware | `Permission::allows` consulta o perfil; não há atalho por e-mail |
| criação/renomeação bloqueada só para o slug | perfil não editável, não listável e não atribuível por outro usuário |
| grupo `Support` bloqueado por e-mail | grupo `Suporte` bloqueado por pertencimento à equipe |

Referência: ADR-033, decisões 4 e 5.

## 4. O que rejeitar — e por quê

| Prática legada | Regra do V7 que a rejeita |
|---|---|
| `PermissionService` próprio, com regras de resolução e CRUD | ADR-033, decisão 2: o Core fornece vocabulário + `Gate` nativo, **sem** serviço próprio |
| `V2PermissionMiddleware` que decide por URL e verbo | ADR-033: cada módulo enforceia a ação; negação por padrão |
| Fail-open para rota não mapeada | ADR-033: “permissão ausente nega por padrão” |
| `read` = qualquer GET/HEAD | ADR-033: `ver` é uma ação, não um método HTTP |
| Bypass por e-mail fixo | ADR-033, decisão 4: perfil de suporte protegido por código |
| Dois RBACs (Sentinel + V2) | ADR-037: sessão nativa sobre `cms_users`; um único modelo de identidade |
| `permissions_v2` como objeto `módulo → string` | ADR-033, decisão 3: `permissoes` é lista de tokens `{modulo}.{acao}` em jsonb |
| Validação só de valor (`none/read/write`) | ADR-033: tokens validados contra o vocabulário na escrita |
| Herança/roles compostos | ADR-033: “sem herança de perfis” |
| Soft delete de perfil | ADR-017/041: dados de seed/roles explícitos; exclusão com tratamento |
| JS inline, `confirm()`, `@php` com função global | ADR-022 (CSP sem inline), ADR-010 (JS mínimo), ADR-009 (UI própria) |
| `dd()` em teste | ADR-038/040: rigor de não regressão e análise estática |
| Rota GET que muta estado | ADR-033: ação explícita; nada de mutação por leitura |

## 5. Comparação direta com o ADR-033

| Dimensão | Legado (`lotep/V2`) | ADR-033 (V7) |
|---|---|---|
| Vocabulário | `none`, `read`, `write` | `ver`, `editar`, `aprovar`, `arquivar` |
| Unidade | módulo (string do diretório) | `{modulo}.{acao}` |
| Armazenamento | `v2_roles.permissions_v2` (mapa jsonb) | `core_roles.permissoes` (lista jsonb, `CHECK array`) |
| Vínculo | `users.v2_role_id` (UUID) | `cms_users.role_id` (um perfil, FK `nullOnDelete`) |
| Decisão | `PermissionService::hasPermission` + middleware por URL | `Gate` nativo + `Permission::authorize` no módulo |
| Default | fail-open fora de módulo; `none` dentro | **deny-by-default** em tudo |
| Leitura vs escrita | método HTTP | ação declarada |
| Suporte | bypass por e-mail em três pontos | perfil reservado, grupo invisível, sem bypass |
| Validação | valor na escrita, chave livre | token válido do manifesto, sem duplicata |
| Auditoria | `V2AuditLogger` por operação | `AuditWriter` global com `before/after` |
| Ativação | `v2_module_flags` + `module.active` | dado em `core_module_discovery_settings` (ADR-034) |
| UI | Bootstrap + JS inline | Tailwind/daisyUI, CSP sem inline (ADR-009/022) |

## 6. O que serve **apenas** como referência de UX

Os itens abaixo não devem ser copiados como código, apenas como intenção de
interface:

1. **Layout da matriz**: tabela com módulo, grupo e controles por linha.
2. **Três estados visuais** com cores (vermelho/amarelo/verde) — no V7 viram
   checkboxes por ação, sem prometer um “nível” que não existe.
3. **Contagem de operadores vinculados** por perfil, com bloqueio visual de
   exclusão.
4. **Busca por nome/slug** e ordenação por coluna.
5. **Badges de papel** na lista de usuários — no V7 há apenas perfil, sem
   distinção V1/V2.
6. **Mensagem de erro genérica** ao tentar gravar sem permissão.
7. **Gerador de slug** a partir do nome (comportamento, não o script inline).

Tudo o mais — modelos, serviço, middleware, aliases, bypass — é dívida, não
patrimônio.

## 7. Plano de aproveitamento por arquivo

| Arquivo legado | Ação no V7 | Observação |
|---|---|---|
| `V2/Modules/Infraestrutura/GestaoPermissoes/ModuleProvider.php` | reescrever | declarar `permissions` suportadas e rota; sem `active_pattern` |
| `V2/Modules/Infraestrutura/GestaoPermissoes/Routes/cms.php` | reescrever | verbos REST; middleware de ativação + `Permission` |
| `V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesRolesController.php` | reescrever | Form Requests, Policy, tokens; sem `v2_can` |
| `V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesPermissionsController.php` | reescrever | remover `role_id` Sentinel; proteger alvo suporte/super-admin |
| `V2/Modules/Infraestrutura/GestaoPermissoes/Views/roles` | adaptar | componente de matriz; sem `@php` de ícones |
| `V2/Modules/Infraestrutura/GestaoPermissoes/Views/permissions` | adaptar | sem select de papel V1; badge único de perfil |
| `V2/Core/Services/PermissionService.php` | **não portar** | substituído por `Core\Access\Permission` + `Gate` |
| `V2/Core/Middleware/V2PermissionMiddleware.php` | **não portar** | substituído por autorização por ação |
| `V2/Core/Models/V2Role.php` | **não portar** | novo model de `core_roles` |
| `V2/Core/Helpers/helpers.php` (`v2_can`) | **não portar** | `@can`/`Gate` |
| `tests/Feature/V2/Rbac/CommonUserModuleAccessE2ETest.php` | adaptar | virar teste de matriz de tokens e guard 404 do grupo suporte |

## 8. Critérios de aceite do aproveitamento

- Nenhum `PermissionService`, `V2Role`, `v2_can` ou `v2.permission` no código
  novo.
- `core_roles.permissoes` só aceita tokens `{modulo}.{acao}` válidos, sem
  duplicata (ADR-033, verificação).
- Usuário sem token recebe 403 em qualquer ação, inclusive GET.
- Grupo `Suporte` responde 404 para quem não é da equipe, mesmo com o perfil
  mais alto.
- Perfil `suporte` não é editável, listável nem atribuível por outro usuário.
- Mudança de perfil/usuário aparece na trilha de auditoria com `before/after`.
- A matriz da UI reflete exatamente o manifesto: nenhuma ação oferecida sem
  declaração, nenhuma declaração escondida.
