# ADR-054 — Dono único de VIAP/cotas: P17 escreve, P28 lê por porta

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-23) |
| **Data** | 2026-10-23 |
| **Plano de origem** | `wiki/implementation-phases/P17-obras-integridade.md`, `P28-legislativo-completo.md` |
| **Relacionadas** | ADR-001, ADR-013, ADR-015, ADR-016, ADR-018, ADR-019, ADR-033, ADR-035 |

## Contexto

VIAP e cotas aparecem em dois contextos: a integridade pública (P17, com valores e vínculos) e o
perfil legislativo (P28, com a leitura por parlamentar/verba). Se cada fase criar sua tabela, os
números divergem, a correção vira dupla digitação e o cidadão vê duas versões do mesmo dado.

As regras de fronteira (ADR-013) proíbem `modules/*` importar `modules/*`; portanto, “P28 lê P17”
não pode ser um `use` direto no model. A comunicação precisa ser porta Core, widget ou link — não
uma segunda cópia.

## Decisão

1. **P17 é o dono único:** `Transparencia/Viap` (P17) é dono das tabelas, dos valores, da escrita e
   da validação de VIAP/cotas. Nenhuma outra fase cria tabela equivalente.
2. **P28 não escreve:** P28 apenas apresenta. Se precisar do dado embutido na página, consome uma
   **porta read-only no Core** (DTO agregado por exercício/parlamentar) registrada por P17 em
   `ModuleProvider::register()`. Se só precisar referenciar, usa a rota ou um widget do P17.
3. **Sem import entre módulos:** P28 nunca importa `Modules\Transparencia\Viap`; a porta é o único
   caminho de leitura e não expõe model/Eloquent.
4. **Sem cópia/stale:** não há tabela/materialização de cotas em P28. Se P17 estiver inativo
   (ADR-034), a seção é omitida; não se usa cache antigo como fallback.
5. **Valores e PII:** valores em `numeric`, exercício explícito, dados públicos; nomes/funções
   seguem a allowlist de P16/P17 e não incluem CPF/matrícula/endereço. A leitura pública não expõe
   informação individual além da legalmente publicável.
6. **Uma permissão de escrita:** a escrita continua sob as permissões de P17; P28 não ganha
   permissão de edição de VIAP.

## Justificativa — alternativas descartadas

| Alternativa | Por que não |
|---|---|
| Tabela de cotas em P28 | Duas verdades; correção dupla; totais divergentes. |
| P28 importando o model de P17 | Viola ADR-013 e acopla módulos. |
| Copiar via job/ETL | Atrasa e cria stale; o dado é pequeno e consultável. |
| Widget/link em vez de porta | Aceitável para referência, mas não serve quando P28 precisa do valor no layout. |
| Porta no módulo P17 | Módulo não pode ser importado por outro; a porta fica no Core. |

## Consequências

**Positivas**
- Um único conjunto de números para VIAP/cotas.
- P28 apresenta sem duplicar tabela, permissão ou validação.
- Ativação de módulo continua valendo; se P17 sai, a seção não mente.

**Custos / riscos**
- A porta read-only precisa de DTO estável e teste de contrato.
- P17 precisa estar ativo para P28 exibir o bloco; isso é intencional.
- A agregação por parlamentar precisa de índice e teste de N+1.

## Exemplo mínimo

```php
// Core\Contracts\Ports\ViapQuotas (read-only)
interface ViapQuotas
{
    public function byYear(int $year): ViapYear;
    public function byParliamentarian(string $id, int $year): ViapQuota;
}
```

```php
// P17 ModuleProvider::register()
app(ViapRegistry::class)->register(ViapQuotasSource::class);
```

## Verificação

- P28 exibe os mesmos valores de P17 para o mesmo exercício/parlamentar.
- Nenhum `use Modules\Transparencia\Viap` em P28; `tools/arch-check` verde.
- P17 inativo remove a seção de P28; nenhum valor stale aparece.
- Escrita em VIAP só pelas permissões de P17; P28 não tem rota de mutação.
- Totais/por parlamentar não geram N+1; valores são `numeric` e data de atualização é do dado.
