C141 — Perfis de permissão por assessor (visão × edição), configuráveis por coordenador/candidato #110

Merged
fsolla merged 3 commits from C141-perfis-de-permissao-por-assessor-visao-edicao-configura into main 2026-08-20 01:24:56 +00:00
Owner

C141 — Perfis de permissão por assessor (visão × edição), configuráveis por coordenador/candidato

Closes #105

O que entrega

  • Modelo: campaignUser.visibility (carteira|tudo) e campaignUser.editing (carteira|tudo|somente_leitura), default carteira nos dois — contas novas e existentes mantêm o comportamento atual até configurarem. Migration 20260819_214755_add_campaign_user_advisor_permission_profile (sem backfill).
  • Configuração (3 pontos, /campanha/assessores): coluna "Permissão" (badge → popover com Visão/Edição + nota de demandas + Salvar), seção no detalhe [id] e perfil no draft de novo assessor. Vocabulário único client-safe em src/lib/campaignAdvisorProfile.ts (labels, opções, coerência — consumido por zod, collection e UI).
  • Enforcement fail-closed no servidor:
    • Leitura: prólogo novo resolveProfileScopedRead (municípios, lideranças, updates, pledges, atividades, contatos derivados) — Visão "Tudo" alarga só as superfícies não sensíveis.
    • Escrita: eixo de Edição (advisorEditingAccess) em todos os canUpdate*/canCreate*; giro e create individual de atividade checam getWritableMunicipalityIds (Payload não expressa per-município no create); ficha de contatos agora autoriza pelo escopo de escrita (assertPersonContactWritable) — antes "você edita o que vê", que a Visão "Tudo" alargaria em escrita de PII.
    • Caps mantidos: demandas seguem regra própria (C143), apoiadores (PII) e telefones de contas ficam na carteira mesmo com Tudo; escritas de coordenação (contas, municipality.advisors, nível de envolvimento E14, allocationDecision) restritas.
    • Coerência: Edição "Tudo" sem Visão "Tudo" é rejeitada no zod, na action e no hook enforceCoherentAdvisorProfile (REST/admin incluídos); self-service não altera o perfil.

Testes

  • tests/unit/campaignAdvisorProfile.unit.spec.ts (labels/opções/coerência) + accessShared.unit.spec.ts (eixo de edição, prólogo profile-aware).
  • tests/int/campaignAdvisorPermission.int.spec.ts (15 testes): default, set por coordinator/candidate, coerência (action + API), widening de leitura por id exato, caps de demandas/apoiadores/contatos, eixos de escrita (carteira/tudo/somente leitura), giro (write-scope + polos), atividade avulsa, liderança via action, municipalityUpdate, hook parcial.

Gates

pnpm push completo (lint, typecheck, unit 2421, format, knip, cycles, changelog, build) verde; e2e roda no CI.

## C141 — Perfis de permissão por assessor (visão × edição), configuráveis por coordenador/candidato Closes #105 ### O que entrega - **Modelo:** `campaignUser.visibility` (`carteira|tudo`) e `campaignUser.editing` (`carteira|tudo|somente_leitura`), default `carteira` nos dois — contas novas e existentes mantêm o comportamento atual até configurarem. Migration `20260819_214755_add_campaign_user_advisor_permission_profile` (sem backfill). - **Configuração (3 pontos, `/campanha/assessores`):** coluna "Permissão" (badge → popover com Visão/Edição + nota de demandas + Salvar), seção no detalhe `[id]` e perfil no draft de novo assessor. Vocabulário único client-safe em `src/lib/campaignAdvisorProfile.ts` (labels, opções, coerência — consumido por zod, collection e UI). - **Enforcement fail-closed no servidor:** - *Leitura:* prólogo novo `resolveProfileScopedRead` (municípios, lideranças, updates, pledges, atividades, contatos derivados) — Visão "Tudo" alarga só as superfícies não sensíveis. - *Escrita:* eixo de Edição (`advisorEditingAccess`) em todos os `canUpdate*`/`canCreate*`; giro e create individual de atividade checam `getWritableMunicipalityIds` (Payload não expressa per-município no create); ficha de contatos agora autoriza pelo escopo de **escrita** (`assertPersonContactWritable`) — antes "você edita o que vê", que a Visão "Tudo" alargaria em escrita de PII. - *Caps mantidos:* demandas seguem regra própria (C143), apoiadores (PII) e telefones de contas ficam na carteira mesmo com Tudo; escritas de coordenação (contas, `municipality.advisors`, nível de envolvimento E14, `allocationDecision`) restritas. - *Coerência:* Edição "Tudo" sem Visão "Tudo" é rejeitada no zod, na action e no hook `enforceCoherentAdvisorProfile` (REST/admin incluídos); self-service não altera o perfil. ### Testes - `tests/unit/campaignAdvisorProfile.unit.spec.ts` (labels/opções/coerência) + `accessShared.unit.spec.ts` (eixo de edição, prólogo profile-aware). - `tests/int/campaignAdvisorPermission.int.spec.ts` (15 testes): default, set por coordinator/candidate, coerência (action + API), widening de leitura por id exato, caps de demandas/apoiadores/contatos, eixos de escrita (carteira/tudo/somente leitura), giro (write-scope + polos), atividade avulsa, liderança via action, municipalityUpdate, hook parcial. ### Gates `pnpm push` completo (lint, typecheck, unit 2421, format, knip, cycles, changelog, build) verde; e2e roda no CI.
fsolla added 1 commit 2026-08-19 23:07:46 +00:00
feat(C141): perfis de permissão por assessor — Visão × Edição configuráveis, enforcement fail-closed (Closes #105)
Some checks failed
CI (PR) / checks (pull_request) Successful in 12m27s
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 41m22s
6251167db0
fsolla force-pushed C141-perfis-de-permissao-por-assessor-visao-edicao-configura from 6251167db0 to ad0af982a5 2026-08-19 23:17:36 +00:00 Compare
fsolla added 1 commit 2026-08-19 23:23:20 +00:00
plans(C144): options write-scoped para formulários de escrita pós-C141
Some checks failed
CI (PR) / checks (pull_request) Failing after 13m16s
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 15m10s
d320b7e667
fsolla force-pushed C141-perfis-de-permissao-por-assessor-visao-edicao-configura from d320b7e667 to 13d5f57311 2026-08-20 01:09:26 +00:00 Compare
fsolla merged commit 9ae34b683f into main 2026-08-20 01:24:56 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: fsolla/teqo#110
No description provided.