Voltar para os textos

Log não é uma coisa só

Operação, concessão e acesso têm volume, retenção e criticidade diferentes. E nem toda falha de trilha deve ter a mesma consequência.

Quase todo sistema tem uma tabela audit_log com uma coluna JSONB onde cabe tudo: login, erro, mudança de configuração, concessão de role, abertura de documento. Funciona até alguém fazer uma pergunta de governança de verdade, e aí consultar vira arqueologia.

Eu separo a trilha em três planos, porque eles respondem perguntas diferentes:

PlanoPerguntaExemplos
OperacionalO que aconteceu no sistema?login, erro, configuração, processamento
ConcessãoQuem deu qual poder a quem, e quando?roles, grants diretos, shares, revogações
AcessoQuem viu ou consultou qual conteúdo?abertura, download, impressão, consulta a microdados

Cada plano tem volume, retenção, índices e criticidade próprios. Concessão é pouco volume e muito valor probatório. Acesso é volume alto e pede partição por tempo. Operacional é ruído útil. Misturar tudo faz uma trilha importante depender de um writer pensado para outra finalidade.

Estado não é evento

Uma tabela user_role diz quem tem a role hoje. Ela não diz quem tinha há três meses. Para isso existe uma tabela append-only de eventos. As duas são necessárias, e uma não substitui a outra.

Uma tabela de estado diz quem tem acesso. Só a trilha de eventos diz como ele chegou lá.

Assimetria de falha

Nem toda falha de auditoria deve ter a mesma consequência:

NaturezaSe a trilha falharPor quê
operacionalbest-efforto fato pode já ter ocorrido
concessãofalha fechadapoder invisível é inaceitável
acesso sensívelfalha fechadaacesso sem evidência quebra accountability

Isso é uma decisão regulatória e operacional, e tem custo: fail-closed coloca o storage da trilha no caminho crítico. Antes de adotar, meça p50 e p99 do insert, a taxa de erro causada pela trilha, o crescimento diário, o bloat e o comportamento do pool. A política não pode exigir uma disponibilidade que a infraestrutura não foi dimensionada para sustentar.

Concessão e evento na mesma transação

BEGIN;
  INSERT INTO grant_event (...);
  INSERT INTO user_role (...);
  UPDATE app_user
     SET permission_version = permission_version + 1
   WHERE id = ...;
COMMIT;

Se qualquer etapa falhar, rollback de tudo. Um evento que afirma uma concessão que não existe é tão ruim quanto uma concessão sem evento. Sem Unit of Work global, um helper estreito resolve:

security_transaction(operation):
    begin
    operation(shared_session)
    commit_or_rollback

E a regra que dá dente ao helper: qualquer call site de concessão fora dele é bug estrutural, e o CI procura por isso.

Registrar antes de servir

Quando acesso sensível é fail-closed, a ordem é autorizar, registrar o evento e só então serializar e devolver o conteúdo. O erro residual é registrar um acesso que não chegou ao usuário porque algo falhou depois. Esse erro “para mais” é auditável. O inverso, conteúdo entregue sem evento, é exatamente o que a política quer impedir.

Registre a via

Quando alguém abre um recurso, vale gravar por qual caminho a autorização foi satisfeita:

access_via = own_partition | share | all_partitions | platform_bypass | legacy

Se o share for revogado amanhã, a trilha continua explicando o acesso de hoje sem precisar reconstruir o estado passado. Quando mais de uma via é verdadeira ao mesmo tempo, defina uma precedência e mantenha, ou grave todas. O importante é não deixar ambiguidade histórica.

O que não registrar

Polling de interface não é acesso. Listar metadados e abrir conteúdo são eventos diferentes, e gravar cada refresh automático gera volume enorme com pouco valor probatório. Defina o que conta como acesso antes de ligar o writer.

E lembre que a própria trilha é dado pessoal: identidade, IP, horário, objeto acessado, padrão de uso. Ela precisa de controle de acesso, retenção explícita e, nos ambientes mais exigentes, de um usuário de aplicação sem UPDATE nem DELETE sobre ela.