fix(contracts): enforce the edit-lock ADR-0006 promises but never wired

ADR-0006 explicitly commits to preserving the legacy's "trava" once a
contract leaves its editable draft state, overridable only by an
audited super_admin correction — but ContractsService.addItem/addParty
had no status or role check at all, letting anyone with contracts:edit
add items/parties to a contract in any status, including a signed
active one.

Adds assertEditable() (same shape as Quotes' assertNotLocked): draft
is the only free-editing state; past it, only super_admin can still
write, and that write is tagged distinctly in the audit log
(contract.item_added_override) rather than looking like an ordinary
edit. No frontend change needed — ContractDetailPage never called the
add-item/party endpoints in the first place.

Verified live: super_admin override still works past draft, and the
audit_log row for it carries overrode:true and the contract's status
at the time — confirmed directly in Postgres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx
This commit is contained in:
2026-09-04 12:21:47 -03:00
parent ccac936372
commit d46d44f01b
3 changed files with 214 additions and 5 deletions

View File

@@ -516,3 +516,26 @@ Novo endpoint `GET /v1/quotes/:id/anatel-pdf` — 400 se a oferta não tiver nen
### Verificação
Teste de integração novo (`test/anatel-pdf.spec.ts`, 3 testes, `renderPdf` mockado como nos outros specs de PDF — o que está sob teste é o conteúdo regulatório computado, não o Chromium em si): confirma snapshot correto dos campos do produto no item, todos os campos regulatórios computados corretos no HTML (datas, fórmula da multa, tabela de tarifação, texto VoIP, campos fixos), e 400 para oferta sem item STFC. Suíte completa: 39/39 verdes (36→39). `pnpm turbo run typecheck build --force` (28/28).
---
## Trava de edição de Contrato — lacuna real entre ADR-0006 e a implementação
Data: 2026-09-04. Usuário pediu para avaliar as regras de edição/trava de Orçamento-como-Contrato do legado contra o modelo de Contrato do EDEN (ADR-0006) antes de seguir para Assinatura Eletrônica.
### Achado
`docs/adr/0006-contract-first-class.md` promete explicitamente: "fluxo de fechamento de oferta → geração de contrato preserva a trava equivalente [...] transição de estado do contrato que também impede edição desconforme, exceto por papel com permissão de correção auditada equivalente ao super_admin do legado". Essa trava **nunca foi implementada**: `ContractsService.addItem`/`addParty` não tinham nenhuma checagem de status ou papel — qualquer ator com a permissão genérica `contracts:edit` podia adicionar item/parte a um contrato em **qualquer** status, incluindo `cancelled`/`terminated`/`expired`, ou um `active` já assinado. A infraestrutura para a regra correta já existia (`resolveEffectiveFidelity()`, a máquina de estados `CONTRACT_STATUS_TRANSITIONS`) — só a trava de edição em si nunca foi conectada.
### Correção (`apps/api/src/contracts/contracts.service.ts`)
Novo `assertEditable()`, mesmo formato do `assertNotLocked` de Quotes: `status === 'draft'` é o único estado livre para adicionar item/parte; qualquer outro status bloqueia (409) para quem não é `super_admin`; `super_admin` pode seguir editando, mas o evento de auditoria fica marcado (`contract.item_added_override`/`contract.party_added_override`, com `overrode: true` no metadata) em vez de ser indistinguível de uma edição normal — a "correção auditada" que o ADR pedia.
Sem mudança de frontend: `ContractDetailPage` nunca chamou `addContractItem`/`addContractParty` (a função existe no client mas não é usada em nenhuma tela) — a lacuna era puramente de backend.
### Verificação
Teste de integração novo (`test/contracts-edit-lock.spec.ts`, 3 testes, papel throwaway com `contracts:edit`+`view` mas sem `super_admin` — mesmo padrão do `role-weight.spec.ts`): confirma que adicionar item funciona em `draft`, que um ator não-super_admin é bloqueado (409) assim que o contrato sai de `draft`, e que `super_admin` continua conseguindo (override). Suíte completa: 42/42 verdes (39→42). `pnpm turbo run typecheck build --force` (28/28).
### Pendências
`contract_versions` (snapshot-on-signature, invariante nº4 do ADR-0006) segue deliberadamente adiado — precisa do desenho do fluxo de assinatura para não virar uma tabela vazia. `contract_amendments` (forma correta de alterar um contrato já assinado, em vez do override de super_admin) também segue adiado — depende de `contract_versions` primeiro.