Registro, RIPD e um cron que avisa quando morre

Documentos de governança que servem para alguma coisa, prazos de retenção que realmente executam, e a lição de ter tido um job silenciosamente offline por meses.

Última parte da série. Já cobri o diagnóstico, a minimização, a exclusão de conta e o consentimento.

Falta a parte que parece burocracia e é a que mais me fez achar problema: os documentos de governança.

Dois documentos que a lei pede

Registro de operações (art. 37)

O controlador deve manter registro das operações de tratamento que realiza. É o primeiro documento que a ANPD pede numa fiscalização.

Fiz uma escolha que mudou a utilidade dele: escrevi em nível operacional, não jurídico. Cada entrada cita as tabelas, os endpoints e os prefixos de armazenamento de verdade.

A política de privacidade diz “guardamos seus endereços”. O registro diz que eles vivem em user_addresses, em profiles.address e dentro de orders.shipping_address, e que o terceiro é retido por cinco anos enquanto os dois primeiros somem com a conta.

A diferença: o primeiro é para o titular; o segundo é para quem vai mexer no sistema em seis meses e precisa saber onde o dado está. Escrever assim me obrigou a procurar cada lugar — e foi assim que encontrei metade dos problemas.

RIPD (art. 38)

Relatório de impacto. Fiz para as duas operações de maior risco: o estúdio de IA com transferência internacional, e a medição por legítimo interesse.

O valor não está no documento, está no exercício. Enumerar riscos numa tabela com probabilidade e impacto força a admitir coisas que você prefere não pensar.

O risco mais alto que identifiquei no estúdio de IA não é técnico: é o usuário enviar dado sensível apesar do aviso. Não há como impedir sem inspecionar o conteúdo — o que seria, em si, um tratamento mais invasivo que o problema. Então o risco residual é médio, está escrito lá, e a mitigação é orientação explícita em vez de controle técnico.

Admitir um risco residual que você não consegue eliminar é mais honesto que uma lista de mitigações que fingem resolver.

Prazo que não executa é enfeite

Declarar “guardamos por 14 meses” na política e não ter nada que apague é pior que não declarar: vira uma promessa documentada e descumprida.

Escrevi uma rotina que aplica cada prazo — métricas com 14 meses, logs técnicos com 12, contadores com 90 dias, orçamentos sem conversão com 24 meses, pedidos anonimizados após 5 anos.

E aqui vem a parte que me interessa mais.

O cron que morreu sem avisar

Enquanto trabalhava nisso, encontrei um comentário no meu próprio código:

// (keep-alive cron is offline)

Um job agendado que deveria rodar diariamente estava fora do ar. Alguém — eu — percebeu em algum momento, escreveu um contorno, deixou o comentário e seguiu a vida. Não sei por quanto tempo ficou parado.

Isso é o pior tipo de falha operacional: um job silencioso é indistinguível de um job que funciona. Não há erro, não há alerta, não há tela vermelha. Só um efeito que deixa de acontecer.

Aplicado à retenção, o cenário é sério: a rotina para, os dados param de ser eliminados, a política continua prometendo prazos, e ninguém descobre até alguém perguntar.

Duas defesas

Escolher o mecanismo mais simples. O job que morreu dependia de fazer requisições HTTP de dentro do banco — rede, token, endpoint, tudo isso pode falhar. A rotina de retenção é SQL puro: o banco chama uma função do próprio banco. Muito menos modos de falha.

(Nota para quem usa a mesma infraestrutura: Cloudflare Pages não suporta cron triggers — só Workers standalone. Perdi tempo procurando antes de descobrir.)

Tornar o silêncio visível. Cada execução grava uma linha com horário, status e o que apagou. O painel administrativo mostra a última execução e fica vermelho se passar de dez dias — tolerância de uma execução perdida num job semanal.

Isso não impede o job de morrer. Impede que ele morra em segredo, que é o problema de verdade.

Também deixei um endpoint que roda a mesma rotina, protegido por token, para o caso do agendador falhar. Ele faz uma coisa a mais que o SQL não alcança: apagar arquivos órfãos do armazenamento.

Documentar as pendências

O documento de que mais gosto é o de retenção, porque tem uma seção chamada “Pendências conhecidas”.

Dois prefixos de armazenamento continuam públicos. São imagens de personalização que aparecem em páginas públicas da loja — torná-las privadas exigiria rotear a vitrine inteira por um proxy autenticado, mudança grande demais para esta rodada.

Podia ter ficado calado. Preferi escrever: o que está aberto, por quê, qual o risco, e qual o encaminhamento.

Pendência não documentada vira dívida invisível. Documentada, ela tem dono e prazo — e a próxima pessoa que abrir o código não vai achar que aquilo é intencional e definitivo.

A regra que amarra tudo

Escrevi uma regra de manutenção que repeti em três lugares diferentes, porque ela é o que faz a conformidade sobreviver ao próximo commit:

Toda tabela nova com dado pessoal exige: uma entrada no registro de tratamento, uma linha na matriz de anonimização, o tratamento na rotina de exclusão, e um prazo na rotina de retenção.

Sem os quatro, a exclusão de conta passa a deixar rastro. E, como aprendi na parte 3, nada quebra quando isso acontece — o dado simplesmente fica.

O que eu faria diferente

Teria começado pelo registro de tratamento. Comecei consertando o que era óbvio. Se tivesse escrito primeiro o inventário de onde cada dado vive, teria achado os nove problemas em uma tarde, em vez de tropeçar neles ao longo de vários dias.

Teria checado o schema antes de escrever migração. Escrevi um plano inteiro assumindo uma tabela que não existia. Foi pego a tempo, mas por sorte de ter conferido.

Teria testado a correção, não só escrito a correção. Meu plano dizia que restringir a regra de leitura do bucket fecharia o acesso aos anexos de cliente, e classificava isso como “risco zero”. Estava errado. Um bucket marcado como público serve o arquivo por URL sem consultar as regras de acesso — elas só governam listagem e API.

Descobri porque testei: removi a regra inteira e baixei o mesmo arquivo, anonimamente, de novo. 112 KB, HTTP 200. A correção de verdade foi mover os arquivos para um bucket não-público — o que a regra sozinha jamais faria.

A lição não é sobre buckets. É que uma correção não verificada é uma hipótese, e hipótese em conformidade é pior que problema conhecido: você para de procurar.

Teria tratado “em breve” como bug. O botão de excluir conta desabilitado e o [...Conteúdo expandido seria aqui...] nos termos ficaram no ar por meses porque nada quebrava. Hoje trato texto placeholder em produção como bug de severidade alta — porque é exatamente o que um denunciante vê primeiro, sem precisar estar logado.

Vale a pena?

O trabalho foi real: quatro migrações, uma dúzia de arquivos novos, quatro documentos legais reescritos, seis de governança.

Mas grande parte do que fiz melhorou o produto, não só a conformidade. A exclusão de conta era uma funcionalidade faltando. Metade de um megabyte a menos para baixar. Um bug que deixava a tela de configurações inerte. Um bucket de arquivos que estava aberto e não deveria — isso era um problema de segurança que eu teria encontrado tarde demais.

A LGPD me deu uma checklist para procurar coisas que eu não estava procurando.

Se você tem algo no ar coletando dados de usuário, o exercício da parte 1 leva uma tarde: quais dados eu guardo, por que, por quanto tempo, para quem vão, e o que acontece se alguém pedir para sair.

Os documentos que escrevi ficaram todos públicos, em /docs/legal. Se servirem de ponto de partida para o seu, ótimo.