Pular para conteúdo

0040 — Assinou, fecha o dossiê: alterar arquivo vira exceção nomeada e visível

Status CONCLUÍDA
Branch spec/0040-dossie-da-garantia-fechado
Criada em 2026-08-19
Apps afetados api · web · e2e
Revisão do problema aprovada na rodada 4
Revisão da experiência aprovada na rodada 4
Revisão do detalhamento fechada na 6ª rodada, por decisão do usuário
Revisão da implementação api aprovada na 4ª · branch aprovada na 1ª

Status: RASCUNHO → APROVADA → EM IMPLEMENTAÇÃO → CONCLUÍDA

A spec tem três partes, escritas em ordem, e quatro portões — cada um revisado por um subagente de contexto limpo, em até 3 rodadas. A máquina de revisão é do plugin superpowers; a régua é a rubrica correspondente em .claude/rubrics/. Achados vêm classificados: qualquer Critical reprova, Important em aberto reprova salvo decisão registrada do usuário, e Minor vira pendência aceita.

  1. A Parte I diz qual é o problema e como se convive com ele hoje. Revisada na seção 6 (necessidade × jornada de hoje). É o portão mais barato de todos, e o único que pergunta contra que problema isto está sendo construído.
  2. A Parte II diz como se usa o que vai existir. Revisada na seção 8 (jornada × viewports).
  3. A Parte III diz como se constrói. Revisada na seção 16 (segurança × clareza técnica) — autorização, isolamento, e se sobrou decisão para quem vai codar.
  4. A implementação é medida contra as três, na seção 17 (aderência × qualidade × testes) — uma revisão por fase, e uma da branch inteira antes de fechar.

Cada parte só se escreve depois que a anterior passou. Descrever tela sobre problema mal enquadrado, ou detalhar endpoint sobre experiência vaga, é o jeito mais caro de descobrir que era outra coisa.


Parte I — O problema

Aqui não entra solução: nem tela, nem endpoint, nem botão. Só o que dói, para quem, e o que se faz hoje. Se você já sabe qual é a tela, guarde para a Parte II.

1. Dor

O dossiê da garantia continua aberto depois que ela passa a valer. Registrado o contrato assinado, a garantia vira o compromisso da garantidora com o inquilino — e os arquivos que a sustentam seguem editáveis: dá para anexar outro, trocar o conteúdo de um já anexado e apagar qualquer um, sem que nada no estado da garantia mude.

O que isso custa aparece no dia em que alguém precisa da garantia:

  • No sinistro, o que se lê pode não ser o que foi aceito. A imobiliária comunica a inadimplência e a garantidora vai ao dossiê conferir o que assinou. Se o arquivo pôde ser trocado depois, o PDF que está lá não prova o que estava lá quando a garantia passou a valer — e a garantidora paga (ou recusa) sobre um documento que ela não consegue datar.
  • A troca não deixa traço nenhum na tela. O cartão do documento mostra nome, tamanho e a data de criação do registro, que a substituição não altera; a data de troca do conteúdo existe no banco e não é exibida em lugar nenhum do app. A linha de histórico "Documento substituído" só é emitida para documento de análise de crédito — em documento de garantia não há linha nenhuma. Sobra o log de auditoria, e audit:list é só do ADMIN_TENANT: o OPERATOR e o ANALYST, que abrem a garantia no dia a dia, não o veem.
  • Apagar pede confirmação, e nada mais. O diálogo pergunta "deseja excluir?" e avisa que não dá para desfazer — mas não há guarda de estado nenhuma: confirmado, o anexo sai da garantia vigente. Só o documento apontado como o contrato assinado é protegido — e apenas ele, e apenas enquanto a garantia está vigente ou vencida: na encerrada, apagá-lo é aceito de propósito (RN13c da spec 0038).

É a mesma família de defeito que a spec 0038 tratou no estado: antes dela, a garantia nascia vigente e a cobrança abria antes de existir contrato. O estado foi fechado; o dossiê não.

2. Quem sente

Quem paga a conta é a garantidora, e sempre no pior momento — no sinistro, que é quando o documento é cobrado.

Exposição e consumo são coisas diferentes, e as duas importam. A exposição é obrigatória e vale para todas: toda garantia que passa a valer fica com o dossiê aberto. O consumo é exceção — só a garantia que chega a sinistro tem os arquivos conferidos —, e é justamente por ser raro que o defeito sobrevive: ninguém olha o dossiê até o dia em que ele é a única prova.

E a conta não para só na garantidora: quem recebe a indenização é a imobiliária, que repassa ao locador. Uma recusa apoiada em documento que não se consegue datar cai sobre ela primeiro.

Quem pode mexer, hoje, na garantia vigente:

Papel Anexar Trocar o arquivo Excluir
ADMIN_TENANT (gestor da garantidora) sim sim sim
OPERATOR (cadastros e criação de análises) sim sim sim
AGENCY_USER (imobiliária) não — fechado pelo fix 0011 não não
ANALYST, VIEWER não (só leem) não não
ADMIN_GLOBAL (plataforma) sim sim sim — e sem filtro de tenant

A imobiliária foi fechada ontem, e por outro motivo: ela não escreve em garantia, em estado nenhum. A porta que sobra é a de dentro da garantidora — e é a que ninguém está olhando, porque quem tem essas permissões é justamente quem se confia.

Não há operação para medir. O produto está no ar desde a spec 0028 sem nenhum cliente, e sinistro nenhum aconteceu; o alcance aqui é de desenho, não de volume: toda garantia que passar a valer fica exposta, porque a porta é a mesma para todas.

3. A jornada de hoje

Como o dossiê fecha hoje — e ele não fecha

  1. A garantia é emitida na análise aprovada e nasce aguardando assinatura.
  2. Alguém da garantidora anexa o contrato assinado no painel de documentos da garantia.
  3. Registra a assinatura apontando aquele arquivo. A garantia passa a vigente, e a cobrança é liberada.
  4. Daqui em diante nada trava. O painel de documentos continua com "Adicionar", e cada cartão continua com substituir e excluir. Medido no dev-remote, numa garantia levada a vigente para o teste, com o token do gestor: anexar outro documento devolve 201, trocar o conteúdo dele devolve 200 e excluir devolve 204.
  5. A única exceção é o próprio contrato apontado: trocá-lo ou apagá-lo devolve 422 SIGNED_DOCUMENT_IN_USE — medido. Todos os outros anexos seguem livres, e o caminho para mexer no contrato existe: aponte outro documento pela correção do registro, e o antigo deixa de estar em uso. Na garantia encerrada nem essa proteção vale: ali a exclusão do contrato é liberada de propósito.

O que se faz fora do sistema

Na montagem do dossiê, nada — e é esse o ponto. Não há conferência combinada, planilha paralela nem cópia guardada noutro lugar: quem abre a garantia acredita no que está no painel, porque não existe outro lugar para olhar. O sistema é a única fonte, e ela é editável.

O sinistro, que é onde a dor aparece, acontece hoje inteiramente fora do produto — não há módulo de sinistro, só os parâmetros de carência e prazo na garantidora. É limite declarado, não lacuna desta spec: quando o sinistro entrar, ele vai perguntar ao dossiê, e a resposta precisa já ser confiável.

O que já se tentou

  • Fechar o estado, na spec 0038. A garantia deixou de nascer vigente, ganhou o registro de assinatura como único ato que a ativa, e o documento apontado passou a ser protegido de troca e exclusão (SIGNED_DOCUMENT_IN_USE). Fechou o contrato — não o dossiê.
  • Fechar a porta da imobiliária, no fix 0011. Ela não escreve mais em documento de garantia, em estado nenhum. Fechou um papel — não o estado.

As duas tentativas mostram o padrão: o dossiê foi tratado peça por peça, e nunca pelo estado da garantia, que é a pergunta que o sinistro faz.

4. Resultado esperado

Depois que a garantia passa a valer, alterar o dossiê deixa de ser rotina e vira exceção — e exceção que aparece. O caminho não é proibir para sempre: é fechar por padrão, abrir por permissão nomeada, e registrar cada alteração onde quem confere consegue ver.

Três coisas mudam (decisões do usuário, 2026-08-19):

  1. Fecha por padrão. Numa garantia vigente, vencida ou encerrada, ninguém anexa, troca nem exclui arquivo pelo caminho comum de documento — o documents:create/update/delete que serve ao resto do sistema deixa de bastar aqui.
  2. Duas permissões abrem, e são só duas. Uma para acrescentar arquivo ao dossiê fechado; outra para trocar ou excluir. São permissões próprias, separadas de propósito: acrescentar faz o dossiê crescer, trocar e excluir apagam o que sustentava a garantia, e quem administra a garantidora pode querer conceder uma e não a outra. Gestor e operador nascem com as duas; os demais papéis, com nenhuma.
  3. Toda alteração fica visível na garantia. Quem, quando e o quê, na tela da garantia, para quem lê a garantia — e não só no log de auditoria, que hoje é o único registro e que OPERATOR e ANALYST não enxergam. É a metade da dor que a trava sozinha não resolveria: sem o histórico, um dossiê alterado por quem tem permissão volta a ser indistinguível de um intocado.

Como se sabe que resolveu, sem métrica instrumentada:

  • Quem não tem as permissões novas não consegue mexer no dossiê de uma garantia que já passou a valer — nem tentando por fora da tela.
  • Quem tem consegue, e a alteração aparece na garantia com autor e data, sem precisar de audit:list.
  • Quem abre a garantia no sinistro sabe dizer se o dossiê mudou depois da ativação — hoje não sabe, porque a substituição não deixa traço visível nenhum.
  • Antes de assinar continua livre: aguardando assinatura é quando o dossiê se monta, e travar ali impediria anexar o próprio contrato que a assinatura precisa.

Duas perguntas que a Parte III precisa responder, e que não são desta seção: se a permissão nova atravessa a proteção do contrato apontado (SIGNED_DOCUMENT_IN_USE) ou empilha com ela; e o que vale para o ADMIN_GLOBAL, que recebe toda permissão nova por construção do seed e que a fronteira plataforma × cliente manda deixar de fora.

O caminho anterior era congelar, e mudou. Até 2026-08-19 esta spec dizia "imutável, com o gestor podendo apenas acrescentar". Caiu por um efeito que a revisão expôs: congelar deixava sem conserto o contrato apontado por engano — e "encerrar e emitir de novo" não existe, porque há uma garantia por análise de crédito, com restrição no banco. Rastrear em vez de proibir resolve a mesma dor sem criar um beco.

5. Fora de escopo

  • Corrigir o registro do contrato assinado. Continua como a spec 0038 desenhou (RN10), sem o limite de "só documento anexado antes da ativação" que uma versão anterior desta spec propôs: com permissão nomeada e histórico visível, a troca do ponteiro é rastreável, que é o que aquele limite tentava garantir travando.
  • As notas da garantia. A observação continua editável na vigente — o pedido é sobre arquivo, e a nota já trava sozinha quando a garantia vence ou é encerrada.
  • Encerrar a garantia. Continua valendo em qualquer estado que não seja encerrada: é registro de decisão, não alteração de dossiê.
  • Cobranças. Nascem justamente com a garantia vigente; travá-las apagaria o faturamento.
  • A exclusão do contrato apontado na garantia encerrada é fechada junto, e isso é reversão consciente. Hoje ela é liberada de propósito (RN13c da spec 0038), sob o argumento de que garantia encerrada não precisa mais do papel. Esta spec fecha os três estados pela mesma régua — a pergunta passa a ser do estado do dossiê, não do papel de cada documento — e quem tiver a permissão de trocar/excluir segue conseguindo, com o histórico registrando.
  • Versionar documento, ou guardar cópia congelada. O histórico diz que mudou, quem mudou e quando — não guarda a via anterior. Recuperar o arquivo substituído é outro problema.
  • Backfill das garantias vigentes sem contrato apontado. São 128 no dev-remote e nenhuma na produção — dado de teste anterior à spec 0038. Decisão do usuário: não tratar. Com as permissões novas elas seguem consertáveis por quem as tem.
  • A mesma pergunta na análise de crédito. O dossiê da análise tem a janela da imobiliária (spec 0036 e fix 0011) e não é o que este pedido trata.
  • Assinatura digital, hash ou carimbo de tempo do arquivo. Provar que o PDF é o original é outro problema, e maior.
  • Alertar alguém quando o dossiê muda. O histórico é consultável; notificação ativa é escopo de outra spec.

6. Revisão do problema

Preenchido pelo laço do /spec new, rubrica .claude/rubrics/problema.md. Registrar todas as rodadas, inclusive as reprovadas.

Rodada Critical Important Minor Veredicto
1 1 1 3 reprovada — laço interrompido por [PERGUNTAR AO USUÁRIO]
2 0 3 4 reprovada — laço interrompido por [PERGUNTAR AO USUÁRIO]
3 0 1 5 reprovada — a trava criava um beco sem saída; o usuário mudou o caminho
4 0 0 3 aprovada

Resultado: aprovada na rodada 4 — três reprovações antes, duas delas interrompidas para decisão do usuário e a terceira porque o caminho mudou.

Pendências aceitas: os três Minor da rodada 4 apontam para conteúdo que a Parte III escreve de qualquer jeito. Dois deles já estão registrados no fim da seção 4 (a colisão com SIGNED_DOCUMENT_IN_USE e o ADMIN_GLOBAL que recebe toda permissão nova); o terceiro era o slug, e foi resolvido renomeando a spec e a branch.


Parte II — A experiência

Não preencher antes da revisão da seção 6 estar aprovada.

7. Jornada pretendida

Ponto de entrada: a tela da garantia, no painel de documentos — o mesmo lugar de hoje. Ninguém navega até aqui para "alterar um dossiê fechado": a pessoa abre a garantia por outro motivo (conferir, cobrar, atender a imobiliária) e se depara com o painel. É por isso que a tela precisa dizer o estado do dossiê antes de ela tentar.

Jornada, caso feliz — e são duas, porque as pessoas são duas:

Quem não tem as permissões novas (a maioria):

  1. Abre a garantia e desce até Documentos.
  2. Lê, no lugar onde havia o botão "Adicionar", por que o dossiê está fechado.
  3. Consulta e baixa o que precisa. Cada documento continua abrindo no visualizador.
  4. Se o dossiê foi alterado depois que a garantia passou a valer, ela vê isso ali mesmo — sem precisar de permissão de auditoria, e sem precisar perguntar a ninguém.

Quem tem (gestor e operador):

  1. Abre a garantia e desce até Documentos. O painel também diz que o dossiê está fechado — a diferença é que oferece a ação, não a esconde.
  2. Anexa o documento novo pelo mesmo formulário de sempre, ou substitui/exclui pelo ícone do cartão. O aviso de que aquilo fica registrado é a própria linha do cabeçalho — ela está à vista o tempo todo. Não se cria diálogo novo para anexar nem para substituir: o primeiro não tem confirmação hoje e o segundo dispara o seletor de arquivo direto do ícone, e inventar um passo cobraria um clique a mais de quem já sabe o que está fazendo. A exceção é excluir, que já tem confirmação e ganha ali a menção ao histórico — é a única ação destrutiva.
  3. Confirma. Um aviso curto confirma a gravação, e a alteração entra no histórico, no topo, com autor e data.

Custo de uso: nenhum campo novo a preencher. O sistema já sabe quem está alterando, quando, qual documento e qual era o estado da garantia — nada disso é perguntado. Não se pede justificativa: exigir um texto por anexo transformaria uma exceção rara em formulário, e a spec 0038 já mostrou que motivo obrigatório se paga onde a ação é destrutiva e definitiva (encerrar), não onde ela é rastreada.

7.1 Desktop

O painel de documentos fica onde está, abaixo de Cobranças, e ganha duas mudanças visíveis.

No cabeçalho do painel, uma linha com o estado do dossiê — texto secundário com ícone de cadeado à esquerda, no peso do blockedReason de hoje e não em tom de alerta: é condição permanente da garantia, não erro, e alerta permanente vira ruído que ninguém lê. Ocupa a largura logo abaixo da contagem (1/20, que não aparece quando o dossiê está vazio — a linha aparece do mesmo jeito) — e não no slot da direita, que hoje é ou o botão "Adicionar" ou a mensagem de bloqueio, um ou outro. Quem tem permissão precisa dos dois ao mesmo tempo: a frase (que pode chegar a ~90 caracteres) embaixo, o botão à direita, como está:

  • "Dossiê fechado — a garantia está vigente. Alterações ficam registradas no histórico." para quem tem as permissões;
  • "Dossiê fechado — a garantia está vigente e os arquivos não podem mais ser alterados." para quem não tem. A frase fica no mesmo lugar nos dois casos — a linha de largura cheia —; o que muda é o slot da direita, onde o botão "Adicionar" aparece para quem pode e some para quem não pode.

O texto nomeia o estado real (vigente, vencida, encerrada), porque "fechado" sozinho não diz desde quando nem por quê.

As permissões são duas e podem ser concedidas separadas, então a faixa e os controles têm quatro combinações — mais a da imobiliária, que não é combinação e sim outra regra:

Quem abre Faixa Oferece
tem as duas "Dossiê fechado — a garantia está vigente. Alterações ficam registradas no histórico." anexar · substituir · excluir
só pode acrescentar "Dossiê fechado — a garantia está vigente. Você pode anexar; alterações ficam registradas." anexar
só pode trocar/excluir "Dossiê fechado — a garantia está vigente. Você pode corrigir os anexos; alterações ficam registradas." substituir · excluir
nenhuma das duas "Dossiê fechado — a garantia está vigente e os arquivos não podem mais ser alterados." nada
imobiliária continua no slot da direita, como hoje: "A garantia é da garantidora". Não ganha a linha nova — para ela nada mudou, e a frase dela é mais restritiva (não escreve em garantia nenhuma, em estado nenhum) nada

Uma seção nova, "Histórico do dossiê", logo abaixo do painel de documentos — não dentro dele: o painel é o que existe agora, o histórico é o que aconteceu. Cada linha traz o que mudou, o arquivo, quem e quando, em ordem decrescente:

Documento anexado · contrato-aditivo.pdf · Marcos (gestor) · 19/08/2026 14:32
Arquivo renomeado · comprovante.pdf → comprovante-2024.pdf · Ana (operadora) · 18/08/2026 09:13
Observação editada · comprovante.pdf · Ana (operadora) · 18/08/2026 09:12
Documento substituído · comprovante.pdf · Ana (operadora) · 18/08/2026 09:10
Contrato assinado corrigido · agora aponta contrato-final.pdf · Marcos (gestor) · 17/08/2026 11:02
Documento excluído · rascunho.pdf · Ana (operadora) · 17/08/2026 16:44

"Arquivo renomeado" não tem botão na tela. O PATCH da API aceita trocar o nome, e nenhum controle do painel o faz hoje — a linha existe porque o evento é possível pela API, e um histórico que só conhece o que a tela oferece deixa de fora exatamente quem entrou por fora.

Renomear e editar observação mostram o par de → para, como o histórico da análise já faz: nesses dois o dado que identifica a mudança é justamente o texto, e só o valor novo não deixa ninguém comparar. Nos outros quatro o nome do arquivo basta.

Os seis tipos de evento são os seis caminhos de escrita, e não é coincidência: o que exige permissão gera linha. Editar observação e renomear entram porque mudam o que o dossiê diz sobre um documento; a correção do contrato apontado entra porque é ela que decide qual arquivo o sinistro procura primeiro — e a seção 5 só a mantém liberada porque o histórico a torna rastreável.

A seção só aparece quando há alteração registrada — garantia cujo dossiê não mudou depois de passar a valer não mostra seção vazia, e essa ausência é a informação: nada mudou.

A imobiliária vê a seção, sem os nomes (decisão do usuário, 2026-08-19): para ela cada linha é "Documento substituído · comprovante.pdf · 18/08/2026", sem autor. Ela precisa saber que o arquivo que baixou pode estar desatualizado; quem mexeu é operação interna da garantidora, e a spec 0038 já escondeu dela a nota interna e o motivo do encerramento pelo mesmo princípio.

O padrão visual é o Historico do design system, o mesmo da análise de crédito (timeline vertical com ícone por tipo de evento). Não se inventa padrão — mas vale saber que o componente de hoje vive dentro da análise e é tipado aos eventos dela: reusar aqui é extrair, não importar.

Sem teto e sem "ver mais": o histórico da análise precisou de agrupamento e limite porque uma devolução gera dez linhas de uma vez; aqui cada linha é uma exceção que alguém decidiu cometer, e uma garantia com trinta linhas é informação, não ruído — a lista longa é o alerta.

Os controles que o painel tem, e de que lado cada um cai:

Controle Com o dossiê fechado
visualizar, baixar sempre — leitura não é alteração
"Adicionar" (formulário de anexo) exige a permissão de acrescentar
substituir (ícone do cartão) exige a de trocar/excluir
excluir (ícone do cartão) exige a de trocar/excluir
"Anexar outra via", no visualizador de arquivo que não abre é um substituir com outro nome — mesma permissão
lápis "Editar observação" e "+ observação" no cartão exige a de trocar/excluir: mudam o que o dossiê diz sobre um documento que já está lá, e o mesmo PATCH renomeia o arquivo

A observação do documento não se confunde com as notas da garantia, que a Parte I deixou fora de escopo: aquelas continuam editáveis na vigente, por outro caminho e outra permissão.

O caminho "enviar arquivo" do diálogo de correção gera duas linhas — "Documento anexado" e "Contrato assinado corrigido" —, porque são dois fatos: entrou arquivo novo no dossiê, e o ponteiro do contrato mudou. Juntar num evento só esconderia metade de quem lê procurando o que entrou.

A segunda porta de escrita fica no diálogo de corrigir o contrato assinado, e ela é fácil de esquecer: além de "escolher um já anexado", ele tem um caminho "enviar arquivo", que anexa um documento novo na garantia. Com o dossiê fechado, esse caminho exige a permissão de acrescentar (decisão do usuário, 2026-08-19) — anexo é anexo, venha por onde vier. Quem tem guarantees:sign e não tem a de acrescentar continua corrigindo, apontando um documento que já está no dossiê.

O caminho "enviar arquivo" fica desabilitado — nos dois viewports —, e não sumido. É a exceção à regra de "não oferecer o que vai ser negado", e ela tem motivo: aqui a opção desabilitada é a explicação. Sumindo, quem não encontra o arquivo certo na lista fica sem entender por que só existe meio diálogo; desabilitada com a frase ao lado, ela diz o que falta e a quem pedir: "Você não tem permissão para anexar arquivo numa garantia que já passou a valer. Escolha um documento já anexado, ou peça a um gestor da garantidora."

E há um beco que precisa de texto próprio: dossiê fechado, sem nenhum anexo, e alguém que pode assinar mas não pode acrescentar. Os dois caminhos ficam indisponíveis — "escolher um já anexado" já nasce desabilitado quando não há anexo. O diálogo então não oferece nada, e diz o porquê: "Esta garantia não tem documento anexado para apontar, e o dossiê está fechado. Peça a um gestor da garantidora para anexar o contrato." É o retrato exato das garantias antigas sem contrato apontado, e é onde a pessoa desiste se a tela não falar.

A confirmação de exclusão continua como está — ela já existe e já avisa que não dá para desfazer. Ganha uma linha a mais: "Esta exclusão fica registrada no histórico da garantia." É a única ação destrutiva do conjunto, e é onde o aviso se paga.

Dossiê fechado e cheio (20/20): as duas coisas convivem, porque ocupam lugares diferentes — a linha do dossiê fechado embaixo, o "Limite atingido" no slot da direita, onde estaria o "Adicionar". Some a faixa seria tirar de quem tem trocar/excluir o aviso de que a alteração fica registrada, justamente quando a saída oferecida é excluir alguma coisa.

Nada desta feature vaza para as outras telas que usam o mesmo painel — inquilino, imóvel e análise de crédito. Vale para os três acréscimos: a linha do dossiê fechado, o aviso de sucesso e a frase "esta exclusão fica registrada no histórico da garantia" no diálogo de confirmação. Todos são condicionados ao painel da garantia; o DocumentsPanel é compartilhado e hoje não emite aviso de sucesso em lugar nenhum.

O que continua igual: o selo de contrato assinado, a ordem que põe o contrato no topo, e o limite de 20 documentos.

Quem concede as duas permissões lê isto, na tela de Perfis de Acesso, onde cada permissão é uma caixa com rótulo e descrição:

Rótulo Descrição
Anexar em garantia fechada Permite acrescentar documentos a uma garantia que já passou a valer. O que já está no dossiê não é afetado, e cada anexo fica registrado no histórico da garantia.
Alterar ou remover documento de garantia fechada Permite substituir o arquivo, editar a observação e excluir documentos de uma garantia que já passou a valer. Apaga ou reescreve o que sustentava a garantia — cada alteração fica registrada no histórico.

Os dois rótulos são lidos lado a lado, numa lista de caixas, e é a única coisa que sustenta a decisão de conceder uma e não a outra: por isso o segundo diz o que ele destrói, e não só o que ele permite.

7.2 Mobile (375px)

A tela da garantia em 375px é uma pilha de seções colapsáveis (MobileDetailSection), e as duas mudanças entram nela sem inventar padrão:

  • A faixa de estado do dossiê aparece dentro da seção "Documentos", acima do primeiro cartão, ocupando a largura. É texto de duas linhas no máximo — em 375px a frase completa ("Dossiê fechado — a garantia está vigente e os arquivos não podem mais ser alterados") quebra em duas e não trunca: truncar o motivo é o defeito que o fix 0010 registrou.
  • "Adicionar" some para quem não tem a permissão, como já acontece hoje para a imobiliária — é o mesmo mecanismo, com outra condição.
  • Nos cartões, os ícones de substituir e excluir somem para quem não tem a segunda permissão. Sobram visualizar e baixar, e os dois alvos de 44px passam a dividir a faixa de ações em duas metades, sem buraco.
  • O histórico do dossiê vira uma seção colapsável própria, "Histórico do dossiê", logo depois de "Documentos" na pilha (Cobertura · Partes · Vigência · Sinistro · Notas · Cobranças · Documentos · Histórico do dossiê), fechada por padrão e com a contagem no título (Histórico do dossiê (3)). Fechada porque quem abre a garantia no celular está atrás do número da cobrança ou do contato, não do histórico; e a contagem no título é o que diz, sem abrir, que houve alteração.
  • Cada linha do histórico quebra em duas no celular: a ação e o arquivo em cima, autor e data embaixo em corpo menor. Uma linha só forçaria truncar o nome do arquivo, que é o dado que identifica o que mudou.

  • A confirmação de que deu certo é o aviso curto no rodapé da tela (o toast que a garantia já usa ao registrar assinatura), e não a linha nova no histórico: em 375px ela cai dentro de uma seção fechada, abaixo de Documentos, e ninguém a veria.

Nada desta feature deixa de aparecer no celular. A garantia é consultada em visita e em atendimento — é onde a pergunta "esse dossiê mudou?" mais aparece.

7.3 Estados

Sem permissão (o caso mais comum). O painel não oferece o que vai ser negado: sem "Adicionar", sem substituir, sem excluir. No lugar, a frase do estado do dossiê. Quem insistir por fora da tela recebe a recusa da API com o mesmo texto — a tela é conveniência, não a trava.

Dossiê aberto (aguardando assinatura). Nada muda em relação a hoje: sem faixa, sem histórico, com todos os botões. É a metade que impede a feature de virar "bloqueia sempre".

Sem alteração nenhuma. A seção de histórico não aparece — no desktop e no mobile. É o estado da esmagadora maioria das garantias, e mostrar "nenhuma alteração" em toda garantia seria ruído permanente para informar exceção.

Carregando. O histórico é uma chamada própria, e o painel de documentos não espera por ela — quem abriu a garantia para baixar um arquivo não fica parado por causa de uma lista de eventos. Enquanto a resposta não chega, a seção não existe na tela: nada de esqueleto. É a escolha certa aqui porque a esmagadora maioria das garantias não tem alteração nenhuma, e um esqueleto que aparece e some em todas elas seria uma piscada permanente para anunciar ausência.

Erro ao alterar. A recusa vai para o alerta que o painel já tem no topo do corpo — não se cria erro por cartão —, com o motivo em português e a saída: "Você não tem permissão para alterar o dossiê de uma garantia vigente. Peça a um gestor da garantidora." Nunca um número de status: é a lição do fix 0010, onde um 413 chegava à tela como "Erro 413".

Erro ao carregar o histórico. A seção aparece — é a exceção à regra de só existir quando há alteração, porque nesse momento ninguém sabe se há — com o título sem contagem ("Histórico do dossiê"), aberta no celular em vez de colapsada, dizendo que não conseguiu carregar e oferecendo tentar de novo. E o painel de documentos continua funcionando. Um histórico que não carrega não pode derrubar a consulta ao dossiê, que é o motivo pelo qual a pessoa abriu a tela.

Tem a permissão e ainda assim é recusado. É o caso do documento apontado como contrato assinado, que a spec 0038 protege por si (SIGNED_DOCUMENT_IN_USE). Os dois desfechos possíveis já têm tela, e a Parte III só escolhe qual:

  • Se a permissão nova não atravessa a proteção (o desfecho provável), a recusa aparece com o motivo dela — que é outro — e o caminho: corrigir o registro da assinatura, não apagar o arquivo.
  • Se atravessa, a garantia pode ficar vigente com o contrato apontado apagado. Aí o lugar do selo "Contrato assinado" passa a mostrar "O contrato assinado foi removido do dossiê", em tom de alerta, e o diálogo de correção abre com o caminho de apontar outro documento — porque o estado é recuperável e a tela não pode deixá-lo mudo.

A garantia foi encerrada enquanto a tela estava aberta. A alteração é recusada pela API com o estado real, e a tela não finge que deu certo: mostra a recusa e recarrega o painel.

8. Revisão da experiência

Preenchido pelo laço do /spec new, rubrica .claude/rubrics/experiencia.md. Registrar todas as rodadas, inclusive as reprovadas.

Rodada Critical Important Minor Veredicto
1 0 5 4 reprovada — laço interrompido por [PERGUNTAR AO USUÁRIO]
2 0 3 4 reprovada — laço interrompido por [PERGUNTAR AO USUÁRIO]
3 0 2 5 reprovada
4 0 0 8 aprovada

Resultado: aprovada na rodada 4.

Pendências aceitas: dos oito Minor da rodada 4, sete foram fechados no próprio texto (a frase da imobiliária, o par de → para, o texto literal da recusa no diálogo, as duas linhas do "enviar arquivo", a ressalva do diálogo de exclusão na jornada, o título da seção em erro e o peso visual da linha de estado). Fica aceito o oitavo: a Parte II cita guarantees:sign, SIGNED_DOCUMENT_IN_USE e o PATCH da API — resíduo técnico numa parte que deveria falar só de experiência, e que some quando a Parte III assumir esses nomes.


Parte III — Detalhamento

Não preencher antes das revisões das seções 6 e 8 estarem aprovadas.

9. Regras de negócio

RN1 — O dossiê fecha quando a garantia passa a valer. Toda escrita sobre documento cujo dono é uma garantia em ACTIVE, EXPIRED (derivado) ou TERMINATED é recusada, salvo pelas permissões da RN3. Em PENDING_SIGNATURE nada muda: é quando o dossiê se monta.

RN2 — A regra é do estado da garantia, não do papel do documento. Vale para qualquer documento cujo guarantee_id aponte para a garantia fechada — inclusive os que hoje não têm proteção nenhuma, e inclusive o contrato apontado, que já tinha a sua (RN13c da 0038).

RN3 — Duas permissões abrem, e só elas.

Permissão Abre
guarantees:dossier_attach POST /documents com entity_type=guarantee numa garantia fechada — inclusive quando quem chama é o diálogo de assinatura pelo caminho "enviar arquivo", que é o mesmo endpoint
guarantees:dossier_amend PUT /documents/{id}/content, PATCH /documents/{id} e DELETE /documents/{id} numa garantia fechada

Ter a permissão nova não dispensa a de base: quem não tem documents:create continua sem anexar em lugar nenhum. As novas são um segundo portão, não um atalho.

RN4 — As permissões da RN3 não atravessam a proteção do contrato apontado. O documento marcado como contrato assinado continua recusando troca e exclusão com SIGNED_DOCUMENT_IN_USE enquanto a garantia estiver vigente ou vencida — o caminho para trocá-lo é corrigir o registro da assinatura, e essa ordem é o que impede a prova principal de sumir por um clique. Muda uma coisa: na garantia encerrada, onde hoje a exclusão dele é liberada, ela passa a exigir guarantees:dossier_amend como qualquer outro documento.

RN4a — O SaaS admin é barrado por guarda de código, e só onde ele hoje alcança. As rotas em que ele consegue escrever no dossiê passam a recusá-lo com 403 PLATFORM_ADMIN_CANNOT_AMEND — em qualquer estado, inclusive PENDING_SIGNATURE, e não só no dossiê fechado: a fronteira é sobre de quem é o dado, não sobre quando. E o mesmo vale para as duas rotas de assinatura — POST /guarantees/{id}/signature, que aponta o contrato e torna a garantia vigente, e PUT /guarantees/{id}/signature, que o reaponta. Fechar só a correção deixaria a porta principal destrancada com as janelas fechadas. Isso estreita um risco aceito da spec 0039, que registrou "registrar assinatura, encerrar e anotar continuam alcançáveis pelo ADMIN_GLOBAL" — decisão do usuário, 2026-08-19; encerrar e anotar seguem como estão, e são assunto de outra spec.

Onde a guarda mora, e por que não é "antes de tudo" nas três rotas por id. As rotas PUT /content, PATCH e DELETE recebem só o doc_id: saber que aquele documento é de uma garantia exige carregá-lo — e carregar filtra por tenant, que para o ADMIN_GLOBAL é None sobre coluna NOT NULL. Medido: hoje ele já toma 404 ali, e no POST /documents toma 500 (IntegrityError ao gravar tenant_id nulo). Ou seja, nas quatro rotas de documento ele já não escreve — mal, mas não escreve.

Então a RN4a é cirúrgica, e não uma guarda global:

Rota Hoje Depois
POST /documents com entity_type=guarantee 500 (IntegrityError) 403 PLATFORM_ADMIN_CANNOT_AMEND, logo depois de resolver a entidade no escopo
PUT /content, PATCH, DELETE 404 (o documento não é alcançável) não muda — 404 continua sendo resposta correta, e forçar 403 exigiria dar a ele leitura que ele não tem
POST e PUT /guarantees/{id}/signature funciona — _get_authorized pula o filtro quando o tenant é nulo 403 PLATFORM_ADMIN_CANNOT_AMEND, antes de resolver o documento

É nas duas rotas de assinatura que está o buraco de verdade, e é lá que a guarda nomeada muda comportamento. Nas de documento ela troca um erro feio por um erro correto no POST, e deixa o 404 das outras três como está.

A recusa vale independentemente das permissões que ele tenha — é a mesma forma da RN13 da spec 0039 (PLATFORM_ADMIN_CANNOT_EMIT), e pelo mesmo motivo: o conjunto dele é montado como todas as permissões declaradas, e a fixture de teste concede todas sem consultar o seed. Trancar por subtração de permissão seria uma trava que passa quando alguém concede a permissão de volta. A subtração no seed continua, como reforço e como sinal na tela de perfis — mas quem recusa é o código.

RN5 — A ordem das guardas é: quem escreve, onde escreve, o que escreve. Primeiro o SaaS admin (RN4a), depois a janela da imobiliária (fix 0011), depois o dossiê fechado (RN1), depois o contrato apontado (RN4). A imobiliária nunca chega a ouvir falar em dossiê fechado — ela não escreve em garantia, ponto —, e quem não tem a permissão nova ouve isso antes de ouvir sobre o contrato.

Nas duas rotas de assinatura a guarda da RN4a é a primeira de todas — como na spec 0039, que a pôs antes de resolver a FK e mediu isso com id inexistente; ali o ADMIN_GLOBAL alcança a garantia de qualquer cliente, então resolver primeiro só trocaria o 403 por um 200.

No POST /documents ela vem logo depois da resolução no escopo, e nas outras três rotas de documento não existe — a RN4a explica por quê.

Para todo o resto, antes das guardas vem o escopo. No POST /documents, entity_type e entity_id chegam no corpo: a guarda do dossiê só roda depois de _validate_entity/resolve_in_scope resolver a garantia no escopo de quem chama. Nas outras três, depois do get_with_content / get_metadata escopado. Invertida a ordem, o 403 novo passaria a distinguir "existe e está fechada" de "não existe", e o entity_id de outra garantidora viraria oráculo de existência — a enumeração que a RN4 da spec 0034 fechou. Fora do escopo continua sendo 404.

RN6 — Toda alteração no dossiê fechado gera registro consultável na garantia. Seis eventos: documento anexado, substituído, excluído, observação editada, arquivo renomeado e contrato assinado corrigido. Os dois de texto trazem o par de → para.

RN6a — Os seis eventos de tela saem de cinco ações de log, e o mapa é este:

Ação em audit_logs Evento na tela
DOCUMENT_UPLOADED Documento anexado
DOCUMENT_REPLACED Documento substituído
DOCUMENT_DELETED Documento excluído
SIGNATURE_CORRECTED Contrato assinado corrigido
DOCUMENT_UPDATED, com notes alterada Observação editada
DOCUMENT_UPDATED, com original_filename alterado Arquivo renomeado

Um PATCH que muda os dois campos gera as duas linhas — são dois fatos, e escondê-los num evento só obrigaria a inventar um rótulo que não descreve nenhum. Um PATCH que não muda valor nenhum não gera linha: hoje o log é gravado mesmo assim, e a leitura do histórico descarta eventos cujo de e depois são iguais. Os eventos anteriores a esta spec não têm antes/depois no payload e aparecem como "Documento alterado", sem o par — é o que existe, e inventar valor para preencher a coluna seria mentir sobre o passado.

RN7 — O histórico sai do log de auditoria que já existe. Não há tabela nova: as cinco ações já são gravadas em audit_logs com entity_type="guarantee". O que muda é uma leitura própria, filtrada por garantia e por essas ações, exposta a quem lê a garantia — sem exigir audit:list, que é do administrador e é justamente o que hoje torna o registro invisível.

RN8 — O corte é o registro da assinatura (inclusive ele), e quando não existe a lista é inteira. A comparação é created_at >= a do SIGNATURE_REGISTERED, e o desempate é o próprio evento: o que empatar no mesmo segundo entra. Não é detalhe — o created_at de audit_logs tem resolução de segundo no SQLite da suíte, e um teste que anexa, registra a assinatura e anexa de novo empata os três; com > o evento que a regra quer mostrar sumiria, e a instabilidade seria a mesma dos dois testes intermitentes que o CLAUDE.md registra. Mostrar a mais é o erro seguro: o histórico existe para não esconder alteração. O histórico mostra o que aconteceu depois do SIGNATURE_REGISTERED daquela garantia — mexer durante a montagem do dossiê era o esperado, e listar isso afogaria a exceção no ruído.

Em PENDING_SIGNATURE o endpoint devolve lista vazia e truncated_at_activation: true — o corte existe, é o próprio fato de a garantia não ter passado a valer —, e a seção não aparece: o dossiê está aberto, mexer nele é o esperado, e não há nada de excepcional a mostrar. É o que a Parte II descreve como "dossiê aberto — nada muda".

Fechado, e sem registro de ativação, não há corte: a lista sai completa, com uma linha dizendo "esta garantia não tem registro de quando passou a valer; a lista mostra todas as alterações". São dois casos reais, e nenhum deles é hipotético: as garantias anteriores à spec 0038, que nasceram vigentes sem passar pelo registro (128 no dev-remote), e a garantia encerrada direto de PENDING_SIGNATURE, que a RN15 da 0038 permite. Nos dois o dossiê está fechado pela RN1 — alterar exige permissão — e seria absurdo exigir permissão para uma alteração que o histórico não mostraria.

RN9 — A imobiliária lê o histórico sem os nomes e sem o texto anterior. (O critério aqui é partner_agency_id; no histórico da análise, que é de onde o componente será extraído, o corte de nomes é decisions:read — então o OPERATOR, que não tem essa permissão, não vê nomes lá e verá aqui. A divergência é deliberada: o princípio desta spec é "parceiro externo", não "quem decide".) Para quem tem partner_agency_id, cada evento sai sem autor. Ela precisa saber que o arquivo mudou; quem mexeu é operação interna da garantidora — mesma régua da nota interna e do motivo de encerramento (seção 10.6 da spec 0038). O par de → para da observação também some para ela, pelo mesmo motivo: o texto antigo é anotação de dentro da garantidora, e mostrá-lo daria a ela um histórico de observações que ela nunca teve. O evento continua aparecendo — só sem os dois valores.

RN10 — O PATCH de documento passa a registrar o antes e o depois. Hoje o payload de auditoria guarda só {document_id, filename}; passa a guardar o valor anterior e o novo de notes e de original_filename, que é o que a RN6 exige para mostrar de → para. Sem migration: a coluna é JSON.

RN10a — Fica fora de escopo a reclassificação em massa. Excluir um tipo de documento com reassign_to_id reescreve o document_type_id de todos os documentos vinculados, inclusive os de garantia fechada, sem passar pelas quatro rotas e sem gerar linha de histórico. É administração do catálogo do tenant, não edição de dossiê — e fechá-la exigiria decidir o que fazer com um tipo que não pode ser removido porque uma garantia antiga o usa.

RN11 — Nada disso alcança documento de inquilino, imóvel ou análise de crédito. A regra é sobre guarantee_id; os outros três donos seguem com as regras que já têm.

10. Autorização e isolamento

10.1 Matriz de papéis

Papel Pode Não pode
ADMIN_GLOBAL ler garantia, dossiê e histórico de qualquer garantidora — como já lê hoje, inclusive a auditoria registrar ou corrigir contrato assinado, e anexar documento em garantia (403 nomeado). Trocar, editar e excluir documento ele já não alcança hoje — responde 404, e a spec não mexe nisso. Ele administra a plataforma e não é atalho para dado de negócio do cliente (mesma fronteira da RN13 da 0039). As duas permissões novas não entram no conjunto dele
ADMIN_TENANT as duas permissões novas, no próprio tenant escrever em garantia de outro tenant
OPERATOR as duas permissões novas, no próprio tenant idem
ANALYST ler o dossiê e o histórico anexar, trocar, editar ou excluir — não tem nem as permissões de base de documento
VIEWER ler o dossiê e o histórico qualquer escrita
AGENCY_USER ler o dossiê e o histórico sem os nomes (RN9), da própria carteira qualquer escrita em documento de garantia, em qualquer estado (fix 0011) — e alcançar garantia de outra imobiliária

O negativo que vira teste: ANALYST e VIEWER recebem 403 ao tentar anexar em garantia fechada; AGENCY_USER recebe GUARANTEE_NOT_EDITABLE_BY_AGENCY antes de qualquer conversa sobre dossiê; ADMIN_GLOBAL recebe 403 mesmo tendo, por construção do seed, toda permissão declarada — e é por isso que a RN3 exige que ele seja excluído explicitamente do conjunto.

10.2 Permissões

resource:action Rótulo (_ACTION_NAMES) Papéis
guarantees:dossier_attach Anexar em garantia fechada ADMIN_TENANT, OPERATOR
guarantees:dossier_amend Alterar ou remover documento de garantia fechada ADMIN_TENANT, OPERATOR

O rótulo vem de _ACTION_NAMES e a descrição (os dois textos longos da Parte II) é o terceiro elemento de _PERMISSIONS. Na tela a caixa lê "Garantias — Anexar em garantia fechada", porque o recurso também é traduzido.

As duas são checadas no serviço, não no Depends do router — e isso tem consequência que precisa estar escrita: a checagem depende do estado da garantia, que o router não conhece. O meta-teste test_toda_permissao_definida_e_exigida varre só app/api/v1/routers/*.py e acusaria as duas como órfãs, deixando make test vermelho e make preflight sujo.

A saída é o helper com a mesma assinatura: o serviço chama exigir_permissao(current_user, "guarantees", "dossier_attach", code=…, message=…) — com código e mensagem próprios, porque o ForbiddenError cru sai como FORBIDDEN e o contrato exige GUARANTEE_DOSSIER_CLOSED com o estado interpolado —, e o meta-teste passa a varrer também app/services/*.py, procurando a chamada exigir_permissao( e não dois literais adjacentes — a regex atual colheria qualquer par de strings vizinhas e passaria a "satisfazer" permissões futuras por acidente. Assim a permissão continua rastreável por grep — que é o que o teste mede — sem fingir que a checagem é do porteiro do router.

As duas são exigidas de fato — não existem só no catálogo. dossier_attach é checada em POST /documents; dossier_amend em PUT /content, PATCH e DELETE. Permissão declarada e não exigida responde "sim, está protegido" sem proteger, que é o achado da spec 0035.

O ADMIN_GLOBAL é a exceção explícita. O seed monta o conjunto dele como todas as permissões ({f"{r}:{a}" for r, a, _ in _PERMISSIONS}); as duas novas precisam ser subtraídas ali, ou a fronteira plataforma × cliente cai sozinha na primeira linha de código.

O seed retira o que o código não declara mais (spec 0035), então nada a remover aqui: as duas permissões são acréscimo puro.

10.3 Isolamento por garantidora (tenant)

Nenhuma FK nova vem no corpo. O guarantee_id do documento é lido do próprio registro já carregado com escopo (get_with_content/get_metadata filtram por tenant e por imobiliária), e o novo GET /guarantees/{id}/dossier-history resolve a garantia pelo mesmo _get_authorized que as demais rotas de garantia — 404 para fora do escopo, nunca 403.

A consulta do histórico filtra audit_logs por entity_type == "guarantee", por entity_id da garantia e pela whitelist das cinco ações da RN6a — o entity_type importa porque DOCUMENT_UPLOADED é gravado também para inquilino, imóvel e análise. O SIGNATURE_REGISTERED que define o corte da RN8 é buscado fora da whitelist, numa consulta própria — sem ela viriam CREATED, TERMINATED e SIGNATURE_REGISTERED junto, que são eventos da garantia e não do dossiê. O tenant_id de audit_logs é anulável (ações de ADMIN_GLOBAL), então o filtro é pelo entity_id, com o tenant_id da garantia validado antes — não o contrário.

10.4 Isolamento por imobiliária

O AGENCY_USER alcança a garantia da própria carteira (partner_agency_id), e o histórico segue o mesmo escopo. Garantia de outra imobiliária responde 404 na rota do histórico, como já responde no GET da garantia. A supressão de nomes da RN9 é aplicada no servidor, não na tela: esconder no front deixaria o nome na resposta.

10.5 Plataforma × cliente (SaaS admin)

O ADMIN_GLOBAL lê — inclusive o histórico, com nomes — e não escreve. A leitura está registrada como risco aceito na seção 15: GET /api/v1/audit-logs já lhe entrega os mesmos eventos hoje, porque o filtro de tenant não se aplica a quem não tem tenant. É a mesma decisão da spec 0039 para a emissão: ele administra a plataforma; alterar o dossiê de uma garantia de um cliente é ato de negócio do cliente. A guarda vem antes de resolver o documento, para que a recusa não dependa de o registro existir.

11. Impacto por app

apps/api

  • app/db/rbac_seed.py — duas permissões novas em _PERMISSIONS, concedidas a ADMIN_TENANT e OPERATOR, e subtraídas do conjunto do ADMIN_GLOBAL.
  • app/services/permission_service.py — _ACTION_NAMES ganha dossier_attach e dossier_amend (o rótulo da caixa); a descrição embaixo é o terceiro elemento de _PERMISSIONS, e é onde entram os dois textos longos da Parte II. Sem isso a tela de Perfis de Acesso mostra a caixa com o nome cru da ação (dossier_attach), e a Parte II diz que é a leitura desses dois rótulos lado a lado que sustenta conceder uma e não a outra.
  • app/core/dependencies.py — nasce o helper exigir_permissao(current_user, recurso, acao), que levanta ForbiddenError e é o que o meta-teste passa a procurar. Hoje só existe User.has_permission, usado solto em dois serviços.
  • tests/test_authorization_coverage.py — o meta-teste passa a varrer app/services/*.py procurando a chamada do helper.
  • Swagger das quatro rotas de documento e das duas de assinatura, com os códigos de erro novos — é exigência do CLAUDE.md da API e o preflight não a pega em rota que já existe.
  • app/repositories/audit_repo.py — uma leitura por entidade sem paginação, filtrada pela whitelist de ações; a list_filtered de hoje é paginada com teto de 50.
  • app/services/document_service.py — a guarda do dossiê fechado nas quatro escritas, depois da guarda da imobiliária e antes da do contrato apontado (RN5); e o payload do PATCH com antes/depois (RN10).
  • app/services/guarantee_service.py — nasce o serviço de leitura do histórico, e o register_signature e o update_signature ganham a guarda da RN4a (PLATFORM_ADMIN_CANNOT_AMEND): um aponta o contrato e ativa a garantia, o outro o reaponta — os dois são ato de negócio do cliente. O que ele não ganha é guarda de anexo: o "enviar arquivo" do diálogo é um POST /documents da tela, seguido do PUT, e a guarda do anexo já está no POST.
  • app/api/v1/routers/guarantees.py — GET /guarantees/{id}/dossier-history, com Swagger.
  • app/schemas/guarantee.py — o schema da resposta do histórico.
  • Sem migration, e isso não quer dizer "nada a fazer no deploy". As duas permissões são dado de seed: entram pelo make seed que o /ship roda sempre. Sem esse passo, o código novo sobe e a permissão não existe — o gestor toma 403 e a tela esconde o botão, que é a falha silenciosa que a spec 0038 pagou.

apps/web

  • components/shared/DocumentsPanel.tsx — hoje blockedReason é um prop que faz três coisas de uma vez: mostra a frase, esconde "Adicionar" e esconde substituir/excluir. Esta spec precisa das três separadas — frase com botões, e duas permissões independentes —, então o prop se divide. É onde o AGENCY_USER pode regredir: ela depende dos três efeitos juntos, e há três consumidores do prop — credit-analyses/show.tsx (a janela da imobiliária na análise, do fix 0011) e os dois painéis da garantia. O da análise não muda de regra, mas muda de assinatura, e é onde a regressão passa despercebida.
  • pages/guarantees/show.tsx — o can() desta tela devolve true para o ADMIN_GLOBAL (isAdminGlobal || …), então os controles precisam de condição própria: ele lê, não altera, e oferecer o botão seria convidá-lo a tomar 403. Vale para os dois botões que já existem — "Registrar contrato assinado" e "Corrigir" —, que hoje funcionam para ele e passam a devolver 403 pela RN4a. Passa o estado do dossiê e as permissões ao painel, e monta a seção nova; no mobile, mais uma MobileDetailSection depois de Documentos.
  • pages/guarantees/components/ — o componente do histórico do dossiê, extraído do HistoryTimeline da análise (é extração, não import: o de hoje é tipado aos eventos dela).
  • pages/guarantees/components/SignatureDialog.tsx — o caminho "enviar arquivo" desabilitado com o motivo, e o texto do beco sem anexo.
  • data-testid em tudo que é novo: a linha de estado, a seção, cada linha do histórico e o botão de tentar de novo.

apps/e2e

  • Spec nova cobrindo: quem não tem permissão não vê os controles e recebe 403 pela API; quem tem anexa e a linha aparece no histórico; a imobiliária lê o histórico sem nomes; e 375px — seção colapsada com contagem, sem rolagem horizontal.
  • E os dois casos que a divisão do blockedReason põe em risco: a imobiliária no painel da análise de crédito fora da janela dela (regra do fix 0011, que não muda e não pode quebrar); e a imobiliária abrindo o painel de documentos de uma garantia e não vendo "Adicionar", substituir, excluir, lápis nem "+ observação". É regressão de tela, e o teste de API do fix 0011 não a alcança.
  • Sem seed novo: o mundo é montado pela API, como nas specs 0038 e 0039.

12. Contrato de API

As quatro escritas de documento não mudam de forma — mudam de resposta:

Rota Novo erro
POST /api/v1/documents (entity_type=guarantee) 403 GUARANTEE_DOSSIER_CLOSED sem guarantees:dossier_attach · 403 PLATFORM_ADMIN_CANNOT_AMEND para o ADMIN_GLOBAL
PUT /api/v1/documents/{id}/content 403 GUARANTEE_DOSSIER_CLOSED sem guarantees:dossier_amend
PATCH /api/v1/documents/{id} idem
DELETE /api/v1/documents/{id} idem
POST /api/v1/guarantees/{id}/signature 403 PLATFORM_ADMIN_CANNOT_AMEND para o ADMIN_GLOBAL — não muda para mais ninguém
PUT /api/v1/guarantees/{id}/signature idem

O POST /documents também recusa o ADMIN_GLOBAL com 403 PLATFORM_ADMIN_CANNOT_AMEND, logo depois de resolver a entidade no escopo (RN5) — as outras três rotas de documento seguem respondendo 404 para ele, como hoje (RN4a). A mensagem: "A alteração do dossiê é ato da garantidora. O administrador da plataforma não altera documento de garantia de cliente."

A mensagem é um molde com o estado efetivo interpolado (vigente · vencida · encerrada) e o verbo da ação recusada: "A garantia está {estado} e o dossiê está fechado. Você não tem permissão para {anexar arquivos | alterar os arquivos} — peça a um gestor da garantidora." É este o texto, também na tela: a Parte II descreve o mesmo conteúdo em prosa, e onde as duas divergirem vale o molde daqui.

O histórico do dossiê

Os valores de action são estes sete, e não os de audit_logs — a resposta fala a língua da tela, não a do log:

action De onde vem
DOCUMENT_ATTACHED DOCUMENT_UPLOADED
DOCUMENT_REPLACED DOCUMENT_REPLACED
DOCUMENT_DELETED DOCUMENT_DELETED
DOCUMENT_NOTE_EDITED DOCUMENT_UPDATED com notes alterada
DOCUMENT_RENAMED DOCUMENT_UPDATED com original_filename alterado
SIGNED_CONTRACT_CORRECTED SIGNATURE_CORRECTED
DOCUMENT_CHANGED DOCUMENT_UPDATED anterior a esta spec, sem antes/depois no payload
GET /api/v1/guarantees/{id}/dossier-history        Permissão: guarantees:read

200 {
  // `false` **só** quando o dossiê está fechado e não há `SIGNATURE_REGISTERED`: a lista sai
  // inteira e a tela mostra a linha explicando por quê (RN8). Em `PENDING_SIGNATURE` vem `true`
  // com `items` vazio — o corte existe, e é o próprio fato de a garantia não ter passado a
  // valer. O web não infere nada disto de `signed_at`.
  "truncated_at_activation": true,
  "items": [
    { "id": "uuid:campo",   // o id do `audit_log` mais o campo, porque um `PATCH` que muda
      //                       observação e nome vira **duas** linhas a partir de uma só
      "action": "DOCUMENT_ATTACHED",
      "document_id": "uuid", "filename": "contrato-aditivo.pdf",
      "previous_value": null, "new_value": null,
      "actor": { "id": "uuid", "name": "Marcos" },   // null para AGENCY_USER (RN9)
      "at": "2026-08-19T14:32:00Z" },
    { "action": "DOCUMENT_RENAMED",
      "document_id": "uuid", "filename": "comprovante-2024.pdf",
      "previous_value": "comprovante.pdf", "new_value": "comprovante-2024.pdf",
      "actor": { "id": "uuid", "name": "Ana" },
      "at": "2026-08-18T09:13:00Z" }
  ]
}
Erro Quando
404 garantia de outro tenant, de outra imobiliária, ou inexistente
403 sem guarantees:read

O document_id pode apontar para documento já excluído — a linha existe justamente para dizer que ele foi. Nesse caso a linha não é clicável e o nome do arquivo aparece esmaecido; sem isso, a única linha que importa no sinistro levaria a um 404.

Os campos são em inglês, como o resto dos schemas — e previous_value/new_value em vez de from/to, porque from é palavra reservada em Python e obrigaria um alias no Pydantic.

O filename vem do documento, não do payload. O log de SIGNATURE_CORRECTED guarda só document_id e signed_at; o nome sai de um join com documents, que continua encontrando o registro mesmo excluído — a exclusão é lógica e preserva a linha. Vale para todos os eventos: o nome exibido é o do documento hoje, exceto no DOCUMENT_RENAMED, onde o par de → para é do payload.

Lista completa, sem teto e sem paginação: o volume é a própria exceção (RN6), e uma garantia com trinta eventos é informação, não página. Sem teto, de propósito — como a Parte II decidiu: um teto silencioso mostraria 200 de 250 e afirmaria que é tudo, numa lista cujo propósito é ser prova.

13. Critérios de aceite

  • [x] CA1. Com a garantia em PENDING_SIGNATURE, anexar, trocar, editar e excluir seguem funcionando para quem tem as permissões de base — nada mudou ali.

    O sujeito dos negativos é um perfil customizado, e isso não é detalhe de teste. Depois da 10.2, os dois papéis que escrevem documento no dia a dia da garantidora — ADMIN_TENANT e OPERATOR — têm também as duas permissões novas; o AGENCY_USER tem as de base e não terá as novas, mas é barrado antes (CA7); ANALYST e VIEWER não têm as de base e tomam o 403 genérico do porteiro antes de chegar ao serviço. Então os CA2–CA4 se exercem com um papel criado no teste — com as permissões de base e sem a nova —, como o fix 0011 fez para provar que a proteção do DELETE era da regra e não da matriz de acesso.

  • [x] CA2. Com a garantia ACTIVE, EXPIRED ou TERMINATED, um perfil com documents:create e sem guarantees:dossier_attach recebe 403 GUARANTEE_DOSSIER_CLOSED ao anexar.

  • [x] CA3. Nos mesmos estados, um perfil com documents:update/delete e sem guarantees:dossier_amend recebe o mesmo 403 ao trocar o conteúdo, ao editar observação/nome e ao excluir.
  • [x] CA4. As duas permissões são independentes: um perfil com dossier_attach e sem dossier_amend anexa (201) e não exclui (403).
  • [x] CA5. O ADMIN_GLOBAL recebe 403 PLATFORM_ADMIN_CANNOT_AMEND ao anexar documento em garantia e nas duas rotas de assinatura (registrar e corrigir), em qualquer tenant e em qualquer estado da garantia, mesmo tendo todas as permissões — e o teste usa a fixture que as concede todas.
  • [x] CA6. Nas outras três escritas de documento o ADMIN_GLOBAL continua recebendo 404 — o registro não é alcançável por quem não tem tenant, e o critério existe para que ninguém "conserte" isso trocando por 403 e dando leitura de tabela alheia no caminho.
  • [x] CA7. O AGENCY_USER continua recebendo GUARANTEE_NOT_EDITABLE_BY_AGENCY, e não a mensagem de dossiê fechado — a ordem das guardas da RN5 é verificável pelo código do erro. No DELETE o sujeito é um perfil customizado com documents:delete, como no fix 0011: o AGENCY_USER do seed não tem essa permissão e tomaria o 403 do porteiro antes.
  • [x] CA8. O contrato apontado continua recusando troca e exclusão com SIGNED_DOCUMENT_IN_USE em vigente e vencida, mesmo para quem tem dossier_amend; na encerrada, ele passa a exigir a permissão como qualquer outro.
  • [x] CA9. Cada uma das seis alterações gera uma linha no histórico, com autor, data e — nos dois eventos de texto — o par anterior/novo.
  • [x] CA10. Um DOCUMENT_UPDATED gravado antes desta spec, sem antes/depois no payload, aparece como DOCUMENT_CHANGED ("Documento alterado"), sem o par — e não some da lista.
  • [x] CA11. Alteração feita antes de a garantia passar a valer não aparece no histórico.
  • [x] CA12. Numa garantia fechada sem SIGNATURE_REGISTERED — as anteriores à spec 0038 e a encerrada direto de PENDING_SIGNATURE — a lista sai inteira e truncated_at_activation vem false.
  • [x] CA13. O AGENCY_USER lê o histórico da própria carteira sem os nomes, e recebe 404 no histórico de garantia de outra imobiliária.
  • [x] CA14. O ANALYST, que não tem audit:list, vê o histórico do dossiê.
  • [x] CA15. Na tela, quem não tem permissão não vê "Adicionar", substituir, excluir, lápis, "+ observação" nem "Anexar outra via"; e lê a linha de estado com o estado real da garantia.
  • [x] CA16. Em 375px, a seção "Histórico do dossiê" aparece depois de Documentos, fechada, com a contagem no título, e a tela não rola na horizontal.
  • [x] CA17. Garantia sem alteração nenhuma não mostra a seção — nos dois viewports.
  • [x] CA18. O diálogo de corrigir contrato assinado desabilita "enviar arquivo" para quem não tem dossier_attach, com o motivo; e, sem nenhum anexo, diz o que fazer.
  • [x] CA19. Documento de inquilino, imóvel e análise de crédito não é afetado por nada disto.
  • [x] CA20. A permissão nova não dispensa a de base: um perfil com guarantees:dossier_attach e sem documents:create continua recebendo 403 do porteiro ao anexar.
  • [x] CA20a. A tela da garantia não oferece ao ADMIN_GLOBAL os botões de registrar e corrigir contrato assinado, nem os controles de documento — ele lê, e a tela não o convida a tomar 403.
  • [x] CA21. As telas de /app/admin/* não mudam: o SaaS admin não ganha painel de dossiê nem ação nenhuma sobre documento.
  • [x] CA22. O histórico de garantia de outra garantidora responde 404 — e o de outra imobiliária também (CA13), pelo mesmo caminho.
  • [x] CA23. O ADMIN_GLOBAL lê o histórico de qualquer garantidora, com os nomes; e recebe 403 nas escritas que a RN4a nomeia (CA5).
  • [x] CA24. Anexar com entity_id de garantia de outra garantidora continua respondendo 404, e nunca o 403 novo — a guarda do dossiê roda depois da resolução no escopo.

14. Plano de implementação

  1. apps/api — permissões no seed (com a subtração do ADMIN_GLOBAL), guarda do dossiê nas quatro escritas, payload do PATCH com antes/depois, serviço e rota do histórico, Swagger e docstrings. Testes por RN, com os negativos de papel da 10.1.
  2. apps/web — linha de estado e condições no DocumentsPanel, seção do histórico extraída do HistoryTimeline, ajuste do SignatureDialog, nos dois viewports.
  3. apps/e2e — o spec do fluxo, com o papel sem permissão e o com.

A ordem importa por um motivo concreto: o contrato do histórico desce da API, e a extração do componente de histórico depende do formato do evento.

15. Decisões em aberto

Nenhuma. As seis decisões tomadas durante a escrita estão registradas nas seções 4, 5 e 7, com data.

Riscos aceitos:

  • O histórico depende do log de auditoria continuar sendo escrito. Se um caminho novo de escrita esquecer o audit.log, ele fica invisível no histórico — e a tela dirá que nada mudou. É o preço de não criar tabela própria; o teste de cada RN cobre os seis caminhos de hoje.
  • O ADMIN_GLOBAL lê o histórico do dossiê de qualquer garantidora, e isso é concessão registrada (decisão de 2026-08-19). O precedente que a 10.5 cita — /api/v1/audit-logs — é fechado por audit:list, permissão do administrador da garantidora; a rota nova abre com guarantees:read, que ele tem em qualquer tenant. Ele já enxerga os mesmos eventos pela auditoria, então a informação não é nova; a porta é.
  • A fronteira plataforma × cliente não é fechada por regra nas três rotas por id. Ali o ADMIN_GLOBAL é barrado por efeito colateral do filtro de tenant (404), não por decisão escrita — se algum dia alguém der a ele escopo de leitura nessas rotas, a porta reabre em silêncio. Está registrado; fechar por regra é trabalho de quem tratar a fronteira inteira.
  • O ADMIN_GLOBAL continua podendo encerrar e anotar garantia de cliente. A spec 0039 registrou isso como risco aceito, e esta estreita o que é dossiê: documento e as duas rotas de assinatura. Encerrar e anotar ficam para quem for tratar a fronteira inteira.
  • Não há alerta ativo. Quem não abrir a garantia não fica sabendo que o dossiê mudou. Consultável não é o mesmo que avisado, e a spec escolheu o primeiro.

16. Revisão do detalhamento

Preenchido pelo laço do /spec new, rubrica .claude/rubrics/detalhamento.md. Registrar todas as rodadas, inclusive as reprovadas.

Rodada Critical Important Minor Veredicto
1 2 4 5 reprovada
2 0 6 6 reprovada
3 1 4 5 reprovada — laço interrompido por decisão de escopo do usuário
4 0 2 6 reprovada
5 1 2 7 reprovada
6 0 5 5 reprovada — laço parado no limite, com uma decisão para o usuário

Resultado: fechada por decisão do usuário na 6ª rodada, sem carimbo de aprovação. As duas primeiras rodadas acharam defeito de desenho — a guarda do SaaS admin trancada por permissão em vez de código, e o 403 novo virando oráculo de existência —, e as duas foram corrigidas. Da quarta em diante o laço passou a devolver detalhe de redação e contradição entre seções, sem Critical: o sinal de que o custo de mais uma volta passou o do que ela ainda acha.

Pendências aceitas — o que a rodada 6 apontou e não foi fechado:

  • A garantia vencida não tem caminho para corrigir o contrato apontado. Trocar e excluir batem em SIGNED_DOCUMENT_IN_USE; reapontar bate em SIGNATURE_LOCKED, porque corrigir o registro só vale enquanto vigente (RN10 da 0038). É beco real, herdado, e a Parte II manda a tela instruir esse caminho — que ali está morto. Quem for implementar precisa saber disto: a mensagem da tela, na vencida, não pode prometer a correção.
  • O ADMIN_GLOBAL lê o histórico do dossiê de qualquer garantidora (seção 15), pela guarantees:read que ele tem em todo tenant. Ele já vê os mesmos eventos pela auditoria; a porta é que é nova.
  • A fronteira plataforma × cliente não é fechada por regra nas três rotas de documento por id — ali ele é barrado por efeito colateral do filtro de tenant (404).
  • A reclassificação em massa de tipo de documento (RN10a) atravessa o dossiê fechado sem permissão nova e sem histórico.

17. Revisão da implementação

Preenchido pelo /spec implement, rubrica .claude/rubrics/implementacao.md. As revisões de fase (api, web, e2e) vão nas primeiras linhas; a da branch inteira, na última.

Rodada Escopo Critical Important Minor Veredicto
1 apps/api 0 5 5 REPROVADO
2 apps/api 0 2 6 REPROVADO
3 apps/api 0 1 5 REPROVADO
4 apps/api 0 0 5 APROVADO
1 apps/web 0 2 5 REPROVADO
2 apps/web 0 2 5 REPROVADO
3 apps/web 0 1 4 REPROVADO
4 apps/web 0 1 8 REPROVADO
1 apps/e2e 1 3 6 REPROVADO
2 apps/e2e 0 1 5 REPROVADO
3 apps/e2e 0 1 5 REPROVADO
1 branch 0 0 7 APROVADO

O apps/web gastou quatro rodadas, e é o preço de não ter suíte. Cada correção só é lida pela primeira vez pelo revisor seguinte — tsc verde diz que compila, e nada mais. Três das quatro rodadas acharam defeito que a correção anterior criou, que é a exceção que o skill prevê: a releitura da garantia no caminho de recusa (rodada 3) desmontava o diálogo que carregava a recusa (rodada 4), e o key que abria a seção no erro (rodada 3) a fechava no acerto (rodada 4). O que cada rodada cobrou:

  1. O SaaS admin continuava recebendo os controles de documento na garantia que aguarda assinatura (a exclusão dele estava atrás de dossieFechado), e o par de → para desenhava "vazio → vazio" para a imobiliária — sobre o campo que a RN9 existe para esconder dela.
  2. O botão "Tentar de novo" apagava o bloco que o continha, e o CA20a não tinha teste em fase nenhuma.
  3. A releitura da garantia no caminho de recusa desmontava o ConfirmDialog que acabara de receber a mensagem — a 7.3 pede as duas metades, e chegava só a segunda.
  4. aoAlterar estava condicionado a dossieFechado, o que desligava a releitura exatamente na transição em que ela serve: quem abriu a garantia aguardando assinatura e teve a assinatura registrada por outra pessoa seguiria vendo os botões até recarregar a página.

O apps/e2e gastou três, e as três foram sobre a mesma pergunta: o teste falharia?

  1. Critical — as oito negativas por linha de documento eram inertes: o painel desenha o cabeçalho uma viagem de rede antes das linhas, e era o cabeçalho que as ancorava. Nasceu daí o esperarALinhaDoDocumento, por onde toda negativa passa.
  2. A ausência do lápis era verde por vazio — ele só existe em documento com observação, e nenhum documento do teste tinha. Nasceu daí o comObservacaoEsemObservacao, que monta os dois ramos do ternário.
  3. guarantee-signature-button era afirmado ausente numa garantia vigente, onde a máquina de estados já o esconde de todo mundo. O CA20a virou dois testes, um por estado.

O que cada rodada da API cobrou, e o que fechou:

  1. Metade do histórico sem prova — o corte da RN8 e quatro dos seis eventos da RN6 sem teste que morresse com a regra; o filename do SIGNED_CONTRACT_CORRECTED saindo null; CA6 e CA22 sem asserção.
  2. nomes_por_id consultando sem filtro de tenant_id (contorno do BaseRepository) e um bloco duplicado inalcançável na guarda.
  3. A supressão do par de → para para a imobiliária (RN9) sem teste — regra de confidencialidade sustentada só por leitura.
  4. Aprovada. Os cinco Minor foram triados abaixo; o primeiro deles foi corrigido na mesma rodada, os outros quatro viraram pendência.

Defeitos aceitos (fase apps/api, rodada 4):

  • Corrigido em vez de aceito: a guarda do dossiê falhava aberta quando a garantia não carregava. Hoje ela recusa com 404 e há teste direto no serviço, com red-green verificado (revertida a recusa, o teste falha).
  • DOCUMENT_RENAMED também tem o par de → para suprimido para a imobiliária. A RN9 nomeia a observação; o título dela (“sem os nomes e sem o texto anterior”) admite a leitura mais larga, e o filename atual continua indo. Fica como está.
  • UUID(e.payload["document_id"]) e _EVENTO_POR_ACAO[e.action] estouram se um audit_log chegar fora do formato que os cinco caminhos de escrita gravam. Inalcançável pelo código de hoje.
  • Cobertura mais estreita que a regra em três pontos: dossier_amend testado só na garantia vigente, o SaaS admin só em dois dos quatro estados, e não há negativo de quem não tem guarantees:read no GET /dossier-history.
  • A subtração das duas permissões do ADMIN_GLOBAL no seed não tem teste — a trava real é a guarda de código, que tem.

18. Registro

A ordem foi a do plano — apps/api → apps/web → apps/e2e —, e o que mudou em relação ao planejado está abaixo.

O blockedReason não se dividiu; ganhou dois vizinhos. A seção 11 previa dividir o prop, e o risco que ela nomeava era a regressão do AGENCY_USER nos três consumidores. Dividir o prop era justamente o que criaria esse risco: blockedReason continua inteiro e com o mesmo sentido — a regra do fix 0011, que fecha os três grupos de uma vez —, e as duas permissões novas entraram como podeAnexar e podeAlterar, com default true. As três condições ficaram separadas, que era o objetivo, e o painel da análise de crédito não precisou mudar uma linha.

A extração virou dois componentes, não um. O TimelineItem era o previsto; no caminho apareceu que o par de → para também estava para ser copiado, com o mesmo sx e a mesma justificativa de 375px. Os dois moram em components/shared/Timeline.tsx, e o histórico da análise de crédito passou a consumir ambos.

Um texto que a spec não escreveu, e por que ele existe. A tabela da 7.1 dá quatro frases para a linha de estado, todas para o dossiê fechado. Faltava o caso do ADMIN_GLOBAL em PENDING_SIGNATURE: a RN4a não tem estado, então ele não recebe controle nenhum ali — e sem frase o painel escondia sem explicar, que é o oposto do que a 7.1 decidiu para todos os outros papéis. A frase é "O dossiê é da garantidora. O administrador da plataforma consulta os arquivos e não os altera.", e vale em qualquer estado.

Um desvio consciente da 7.3. Ela manda a recusa de alteração para "o alerta que o painel já tem no topo do corpo". A da exclusão passou a aparecer dentro do próprio ConfirmDialog: o alerta do topo fica atrás do backdrop, e quem confirmou veria o botão sair de "Aguarde…" e mais nada. É a mesma escolha que o diálogo de encerrar já fazia desde a spec 0038.

O sujeito dos negativos de tela teve de ser construído. Depois da 10.2 não existe papel de sistema que assine e não anexe: ADMIN_TENANT e OPERATOR receberam as duas permissões novas, e ANALYST/VIEWER não têm nem as de base. O e2e monta um usuário sem papel de sistema com um perfil customizado de onze permissões, reusado entre execuções e reescrito a cada uma — sem reescrever, mudar a lista não teria efeito enquanto ninguém apagasse o usuário à mão no tenant compartilhado.

Pendências aceitas (o que os portões apontaram e se decidiu não fazer nesta spec):

  • O beco do diálogo de assinatura não tem e2e, e não é escolha: chegar a semSaida exigiria uma garantia vigente sem documento nenhum, e a RN4 recusa excluir o contrato apontado enquanto ela estiver vigente. É o retrato das garantias anteriores à spec 0038, que a seção 5 deixou fora de escopo.
  • As duas frases intermediárias da linha de estado (só dossier_attach, só dossier_amend) não são medidas por suíte nenhuma — trocá-las entre si não deixaria rastro. A independência das duas permissões está provada na API (test_as_duas_permissoes_sao_independentes); o que falta é o texto.
  • O estado de erro do histórico (alerta e "tentar de novo") não tem e2e. É caminho que já regrediu duas vezes durante a implementação, e page.route tornaria o teste barato.
  • O CA18 é medido só no desktop. Em 375px o controle é outro elemento — um Button desabilitado no lugar do Radio —, e nada o exercita.
  • imobiliariaDoTeste pode duplicar a imobiliária na primeira execução de um ambiente limpo, com workers em paralelo. Herdado do e2e da spec 0038; não afeta asserção nenhuma.
  • DOCUMENT_RENAMED também tem o par de → para suprimido para a imobiliária, e a RN9 nomeia só a observação. O título dela ("sem os nomes e sem o texto anterior") admite a leitura mais larga, e o nome atual do arquivo continua indo.
  • A linha do dossiê aparece para o ADMIN_GLOBAL também em PENDING_SIGNATURE, e a 7.3 diz "dossiê aberto: sem faixa". É a divergência descrita acima, deliberada.
  • O nome do arquivo só sai esmaecido na linha de exclusão. Um DOCUMENT_ATTACHED de documento apagado depois continua em cor normal — o contrato não expõe se o documento ainda existe, e a linha que a §12 nomeia (a da exclusão) está certa.
  • A concessão das duas permissões ao OPERATOR não tem teste. O ADMIN_TENANT é exercido pela fixture; o OPERATOR, não. Mesma família da subtração do ADMIN_GLOBAL no seed: a trava real é a guarda de código, que tem prova.
  • Os quatro riscos aceitos da seção 15 seguem valendo, sem mudança.