Conteúdo da trilha
PadrãoSegurança e confiabilidade

Trilha de Auditoria

Audit Log

Trilha de Auditoria (Audit Log) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Segurança e confiabilidade. Registrar quem pediu, o que o modelo viu, o que decidiu e o que executou.

Elemento
62/70
Nível
Intermediário
Leitura
9 min
Atualizado
Nesta página 17
  1. 01Intenção
  2. 02Problema
  3. 03Solução
  4. 04Estrutura
  5. 05Pseudocódigo
  6. 06Cenários reais
  7. 07Aplicabilidade
  8. 08Quando não usar
  9. 09Como implementar
  10. 10Variações
  11. 11Custo, latência e falhas
  12. 12Como avaliar
  13. 13Em produção
  14. 14Segurança
  15. 15Prós e contras
  16. 16Código
  17. 17Teste seu entendimento
  18. ··Perguntas rápidas

Intenção

Registrar, de forma imutável e consultável, quem pediu, o que o modelo viu, o que ele decidiu e o que o sistema executou. Serve para sistemas que decidem sobre dinheiro, dados ou pessoas e precisam responder por cada decisão depois.

Em uma frase
A caixa-preta do avião: ninguém lê durante o voo, e ela é a primeira coisa procurada quando algo dá errado.

Problema

Luana é gerente de compliance de uma fintech. O assistente financeiro do app agenda pagamentos com aprovação do cliente. Um cliente abriu uma reclamação no Banco Central: diz que nunca aprovou um Pix de R$ 2.300.

O time tinha traces de observabilidade, mas eles eram amostrados a 10%, ficavam guardados por 14 dias e não registravam o que o cliente viu na tela de aprovação. O log da aplicação dizia “pix agendado” e um id de usuário. Não havia como provar se o cliente aprovou, o que ele viu quando aprovou, nem por que o assistente sugeriu aquele pagamento.

Observabilidade serve para o time de engenharia entender o sistema. Auditoria serve para responder a um cliente, a um auditor ou a um juiz. As perguntas são diferentes, e o registro também.

A pergunta que este padrão responde: como guardar evidência suficiente para explicar e defender cada decisão que o sistema tomou?

Solução

Crie um registro de auditoria separado dos traces, com quatro propriedades:

  • Completo para decisões que importam. Toda ação com efeito e toda decisão sobre pessoas gera um evento, sem amostragem.
  • Autocontido. O evento guarda o que foi necessário para a decisão: quem pediu, qual entrada (ou referência a ela), versão do prompt e do modelo, o que o modelo propôs, o que foi mostrado ao humano, quem aprovou e o que foi executado.
  • Imutável. Eventos só são acrescentados, nunca editados. Um encadeamento por hash ou um armazenamento com retenção bloqueada torna adulteração detectável.
  • Consultável e com retenção definida. Dá para buscar por cliente, por ação e por período, pelo tempo que a regulação exige, com dados pessoais tratados conforme a LGPD.

Exemplo trabalhado

A reclamação chega em abril. O Pix foi em janeiro. Com o registro certo, Luana busca action=schedule_pix e o id do cliente e monta a história:

event_id:        a9f3...
ts:              2026-01-12T14:32:11Z
actor:           { type: "user", id: "cli_8821" }
action:          schedule_pix
prompt_version:  finance-assistant@7
model:           claude-sonnet-5-5
proposal:        { amount_brl: "2300.00", dest_name: "Fornecedor X", dest_key_type: "cnpj" }
shown_to_human:  { title: "Agendar Pix", amount: "R$ 2.300,00", dest: "Fornecedor X", key: "**.***.***/****-**" }
decision:        approved
reviewer:        cli_8821 (biometria no app)
execution:       { status: "ok", external_id: "E0000000020260112..." }
input_ref:       obj_91c2  (pedido original, em armazenamento separado)

Isso responde três perguntas do Banco Central. Quem pediu: o titular, autenticado. O que ele viu: valor, nome e chave mascarada. O que o banco executou: o id da transação bate com o Pix. Sem shown_to_human, sobra um “pix agendado” que não prova nada. Sem prompt_version, não dá para saber se o assistente daquela semana estava sugerindo o destinatário errado.

O trace de observabilidade daquele dia já tinha sido amostrado para fora e apagado na retenção de 14 dias. Auditoria não pode herdar essas duas propriedades.

Compare os dois registros:

Pergunta Trace Evento de auditoria
Para quem Engenharia, debug Cliente, auditor, juiz
Amostragem 10% é comum Nenhuma nas ações com efeito
Retenção Dias ou semanas O período que a regulação pede
O que o humano viu Em geral não guarda Campo obrigatório
Dá para editar Muitas vezes sim Só acréscimo, hash encadeado

Estrutura

flowchart LR
  U(["1. Pedido do usuário"]):::io --> A(["2. Modelo propõe"]):::llm
  A --> H((("3. Aprovação"))):::human
  H --> X["4. Execução"]:::tool
  U --> E["5. Montar evento"]:::tool
  A --> E
  H --> E
  X --> E
  E --> R["6. Mascarar e assinar"]:::tool
  R --> L[("7. Registro só de acréscimo")]:::store
  L --> Q{"8. Consulta e relatório"}
  1. O pedido chega com a identidade de quem pediu.
  2. O modelo propõe uma ação, com a justificativa.
  3. A aprovação registra o que foi mostrado e a resposta.
  4. A execução registra o resultado e o identificador externo (id da transação, do email).
  5. Um evento junta tudo, com as versões de prompt, modelo e política.
  6. Dados pessoais são mascarados ou separados, e o evento recebe o hash do anterior.
  7. O evento vai para um armazenamento que só aceita acréscimos.
  8. Compliance, suporte e engenharia consultam com permissões diferentes.
Checagem +20 XP de primeiraO time da Luana tem traces a 10% de amostragem e retenção de 14 dias. Por que isso não responde a reclamação do Pix no Banco Central?

Pseudocódigo

schema AuditEvent {
    event_id: string, ts: datetime, actor: { type: "user" | "agent" | "reviewer", id: string },
    action: string, input_ref: string, prompt_version: string, model: string,
    proposal: any, rationale: string, shown_to_human: any?, decision: "approved" | "rejected" | "auto",
    execution: { status: string, external_id: string? }, prev_hash: string, hash: string
}

function record(ctx, proposal, review, execution):
    event = AuditEvent(
        actor = ctx.user, action = proposal.tool, input_ref = store_input(ctx.input),   # 1
        prompt_version = ctx.prompt_version, model = ctx.model,
        proposal = proposal.args, rationale = proposal.reason,                           # 2
        shown_to_human = review.preview, decision = review.decision,                     # 3
        execution = execution,                                                           # 4
    )
    event = redact_pii(event)                                                            # 6
    event.prev_hash = audit_log.last_hash()
    event.hash = sha256(canonical_json(event))
    audit_log.append(event)                                                              # 7

Cenários reais

Aplicabilidade

  • O sistema executa ações com efeito financeiro, legal ou sobre dados de terceiros.
  • Decisões afetam pessoas (crédito, contratação, saúde) e podem ser contestadas.
  • Há regulação que exige guardar evidência por um período.
  • Você precisa responder “por que o sistema fez isso?” meses depois.

Quando não usar

  • Ferramentas internas sem efeito externo, como um resumidor de reuniões. Traces comuns bastam.
  • Como substituto de observabilidade. A auditoria registra decisões; o trace registra tudo o que ajuda a depurar. Os dois convivem. Veja Observabilidade para IA.

Como implementar

  1. Liste as decisões e ações que precisam de evidência, com o time jurídico ou de compliance.
  2. Defina o schema do evento e o que cada campo prova.
  3. Guarde a entrada completa num armazenamento protegido e ponha só a referência no evento, quando ela tiver dados sensíveis.
  4. Registre a versão de tudo que influenciou a decisão: prompt, modelo, política de revisão, regras.
  5. Grave na mesma transação da execução, ou com uma fila que garanta entrega.
  6. Use armazenamento só de acréscimo e encadeamento por hash.
  7. Defina retenção e acesso por papel.
  8. Teste o caminho de consulta: dado um id de cliente, monte a história completa de uma ação.

O que cada campo prova

Escreva o schema como uma lista de perguntas, não como uma cópia do span:

Campo Pergunta que ele responde
actor Quem pediu?
proposal O que o modelo queria executar?
shown_to_human O que a pessoa viu na hora de aprovar?
decision e reviewer Quem autorizou, e como?
execution.external_id O que de fato saiu no banco, no email, no ERP?
prompt_version e model Qual sistema estava no ar?
input_ref Qual pedido original, sem copiar dado pessoal para o evento?

A justificativa que o modelo escreve (rationale) ajuda quem lê daqui a seis meses. Não é prova. Prova é o que entrou, o que foi mostrado e o que saiu.

Cadeia de hash e concorrência

Cada evento guarda o hash do anterior e o próprio hash do conteúdo canônico, sem o campo hash. Se alguém editar um evento no meio, a cadeia quebra na verificação noturna. Isso não impede um administrador com acesso ao disco; impede a edição silenciosa na aplicação.

Gravações paralelas precisam de um único escritor por cadeia (fila, lock, ou uma cadeia por partição, por exemplo por actor.id). Sem isso, dois Pix no mesmo milissegundo apontam para o mesmo prev_hash e a verificação acusa conflito mesmo sem fraude.

Erros comuns

  • Amostrar a auditoria. 10% serve para achar lentidão. Não serve para a reclamação do Pix que caiu nos outros 90%.
  • Retenção de trace. Catorze dias não cobrem o prazo de uma reclamação no Banco Central nem o de um processo trabalhista na triagem de currículos.
  • Logar “pix agendado”. Sem prévia, sem versão, sem id externo, o evento não reconstrói a ação.
  • Dado pessoal no evento. CPF, chave Pix completa e o pedido cru vão para o armazenamento separado, com chave diferente. O evento leva máscara e referência. Aplique Mascaramento de Dados Pessoais antes de gravar.
  • Tratar o rationale do modelo como prova. O modelo descreve o que ele acha que fez. O registro descreve o que o sistema fez.

A trilha começa na refatoração Adicionar Trace. Trace primeiro, para o time ver o sistema. Auditoria em cima, para o que precisa sobreviver ao debug.

Variações

  • Auditoria derivada dos traces. Os spans de decisão são copiados para o registro de auditoria sem amostragem. Menos código, mas cuidado com o que o trace não guarda (o que foi mostrado ao humano).
  • Registro externo. Um serviço de logs com retenção bloqueada pelo provedor, para que nem o time interno consiga apagar.
  • Explicação gerada na hora. Um campo de justificativa curta escrito pelo modelo junto com a proposta. Útil para leitura, mas não é prova: a prova é o que o modelo viu e o que ele propôs.

Custo, latência e falhas

Aspecto O que acontece Como aparece
Latência Gravação síncrona soma milissegundos; assíncrona com fila não soma duração da gravação no span da ação
Custo Armazenamento por anos, maior quando guarda entradas completas volume do registro por mês
Falha: evento faltando Ação executada sem registro conciliação entre execuções e eventos com diferença
Falha: dado pessoal exposto Evento com dado sensível sem máscara achado em auditoria interna
Falha: evento incompleto Falta a prévia mostrada ou a versão do prompt consulta que não consegue explicar a ação

Grave o evento na mesma transação da execução, ou numa fila com entrega garantida e conciliação. Assíncrono sem conciliação é o jeito educado de perder o Pix que mais vai ser contestado.

Os números de armazenamento abaixo são ilustrativos. Suponha 5 mil Pix por dia, 2 KB por evento mascarado, e o pedido completo de 20 KB só no armazenamento separado.

O que você guarda Ordem de grandeza por ano
Só o evento mascarado alguns gigabytes
Evento mais o pedido completo dezenas de gigabytes

Gigabytes por ano são baratos perto de não conseguir responder ao Banco Central. O custo que dói é dado pessoal demais no evento, não o disco.

Checagem +20 XP de primeiraO time adiciona um campo rationale em que o modelo explica por que sugeriu o Pix. Luana quer usar esse texto como prova na reclamação. O que você diz?

Como avaliar

  • Cobertura: toda execução com efeito tem um evento correspondente. Rode a conciliação todo dia.
  • Reconstrução: escolha ações ao acaso e monte a história completa só com o registro. Se faltar algo, o schema está incompleto.
  • Integridade: verifique a cadeia de hashes periodicamente.
{ "check": "reconstruct", "action_id": "pix-88123", "must_include": ["actor", "proposal", "shown_to_human", "decision", "external_id", "prompt_version"] }

O teste de reconstrução é o eval deste padrão. Uma pessoa do time jurídico, sem acesso ao código, tenta contar a história do Pix só com o evento. Se ela pergunta “mas o que o cliente viu?”, falta campo. Se ela pergunta “esse Pix saiu mesmo?”, falta external_id.

Em produção

  • Conciliação diária entre o sistema de execução (banco, email, ERP) e o registro de auditoria.
  • Alerta para qualquer execução sem evento.
  • Acesso ao registro só por papéis definidos, com o próprio acesso auditado.
  • Rotina de resposta a pedidos de titular de dados (LGPD): o que foi guardado sobre a pessoa e por quê.

Segurança

O registro concentra informação sensível. Separe dados pessoais em armazenamento próprio, com chave de acesso diferente, e guarde só referências no evento quando possível. Aplique Mascaramento de Dados Pessoais antes da gravação e defina retenção de acordo com a base legal.

Prós e contras

Prós Contras
Responde a clientes, auditores e reguladores Custo de armazenamento por anos
Mostra exatamente o que a pessoa aprovou Mais um schema para manter junto com o sistema
Base para investigar incidentes e vieses Concentra dados sensíveis
Adulteração fica detectável Exige disciplina para registrar toda ação nova

Código

As duas abas gravam um evento autocontido, encadeado por hash, num armazenamento só de acréscimo.

Python. Use quando as ações rodam em Python e o registro vai para um banco com tabela só de inserção.

import hashlib
import json
import uuid
from datetime import datetime, timezone


def canonical(obj: dict) -> str:
    return json.dumps(obj, sort_keys=True, ensure_ascii=False, separators=(",", ":"))


def record(db, ctx, proposal: dict, review: dict, execution: dict) -> dict:
    prev = db.fetch_one("SELECT hash FROM audit_log ORDER BY seq DESC LIMIT 1")
    event = {
        "event_id": str(uuid.uuid4()),
        "ts": datetime.now(timezone.utc).isoformat(),
        "actor": {"type": "user", "id": ctx.user_id},                  # 1
        "action": proposal["tool"],
        "input_ref": ctx.input_ref,                                    # entrada guardada à parte
        "prompt_version": ctx.prompt_version,
        "model": ctx.model,
        "proposal": proposal["args"],                                  # 2
        "rationale": proposal.get("reason", ""),
        "shown_to_human": review.get("preview"),                       # 3
        "decision": review["decision"],
        "reviewer": review.get("reviewer"),
        "execution": execution,                                        # 4
        "prev_hash": prev["hash"] if prev else "genesis",
    }
    payload = canonical(event)
    event["hash"] = hashlib.sha256(payload.encode()).hexdigest()       # 6
    # 7. a tabela só tem permissão de INSERT para este serviço
    db.execute("INSERT INTO audit_log (event_id, body, hash) VALUES (%s, %s, %s)",
               (event["event_id"], payload, event["hash"]))
    return event

O que você ganhou: cada evento conta a história inteira da ação e a cadeia de hashes denuncia edições. O que você perdeu: gravações concorrentes precisam de serialização (uma fila ou um lock) para a cadeia ficar correta.

TypeScript. Use quando as ações rodam em Node e você grava numa fila antes do armazenamento final.

import { createHash, randomUUID } from "node:crypto";

type AuditEvent = {
  event_id: string; ts: string;
  actor: { type: "user" | "agent" | "reviewer"; id: string };
  action: string; input_ref: string; prompt_version: string; model: string;
  proposal: unknown; rationale: string; shown_to_human: unknown;
  decision: "approved" | "rejected" | "auto"; reviewer?: string;
  execution: { status: string; external_id?: string };
  prev_hash: string; hash?: string;
};

const canonical = (o: unknown): string =>
  JSON.stringify(o, (_k, v) =>
    v && typeof v === "object" && !Array.isArray(v)
      ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)))
      : v,
  );

export async function record(
  log: { lastHash(): Promise<string | null>; append(e: AuditEvent): Promise<void> },
  e: Omit<AuditEvent, "event_id" | "ts" | "prev_hash" | "hash">,
): Promise<AuditEvent> {
  const event: AuditEvent = {
    ...e,
    event_id: randomUUID(),
    ts: new Date().toISOString(),
    prev_hash: (await log.lastHash()) ?? "genesis",
  };
  event.hash = createHash("sha256").update(canonical(event)).digest("hex"); // 6
  await log.append(event); // 7. armazenamento só de acréscimo
  return event;
}

O que você ganhou: o tipo AuditEvent obriga a preencher prévia, decisão e execução. O que você perdeu: o mesmo cuidado com concorrência; serialize as gravações por um único escritor.

Teste seu entendimento

Checagem +20 XP de primeiraO time tem um resumidor interno de reuniões, sem envio para fora e sem decisão sobre pessoas. Alguém pede a trilha de auditoria completa. O que você faz?

Checagem +20 XP de primeiraNa reconstrução de um Pix, o evento tem actor, proposal, decision=approved e external_id. Falta shown_to_human. Qual é o buraco?

Checagem +20 XP de primeiraDois Pix do mesmo cliente gravam ao mesmo tempo e os dois eventos apontam para o mesmo prev_hash. A verificação noturna acusa quebra. Qual é o conserto?

Checagem +20 XP de primeiraUm titular pede, pela LGPD, o que o assistente guardou sobre ele. O evento de auditoria tem a chave Pix completa no campo proposal. Qual foi o erro de desenho?

Perguntas rápidas

O que é Trilha de Auditoria?

Trilha de Auditoria (Audit Log) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Segurança e confiabilidade. Registrar quem pediu, o que o modelo viu, o que decidiu e o que executou.

Quais smells o padrão Trilha de Auditoria ajuda a curar?

Prompt Sem Versão e Voo Cego.

Onde Trilha de Auditoria aparece na prática?

Nos cenários reais Assistente financeiro com aprovação, Triagem de currículos com controle de viés e Assistente jurídico.

Para ir além

Relações

Smells que ajuda a curar 2
Refatorações que chegam aqui 1
Aparece nas trilhas 1
Buscar