
Trilhas de auditoria à prova de adulteração para agentes de IA: Registros de Runtime encadeados por hash, sem dependências, verificáveis por qualquer pessoa.
# halo-record
**Trilhas de auditoria à prova de adulteração para agentes de IA** — Runtime Records encadeados por hash, renderizados como um Runtime Report que os seus clientes podem verificar por si mesmos.
Cada ação que a sua instrumentação captura (chamadas de ferramentas, chamadas de modelos, acesso a dados, aprovações) torna-se um **Runtime Record** num registo apenas de acrescento, encadeado por hash; o **Runtime Report** é essa cadeia renderizada como uma página HTML que recalcula a cadeia de hash no navegador do leitor e renderiza o que apresenta a partir dos registos que verificou (para uma verificação que não depende do próprio código da página, execute `halo verify` sobre o JSONL). Qualquer parte que detenha um checkpoint da cadeia pode verificar que os registos até esse checkpoint não foram alterados desde que foi obtido, sem confiar em quem os produziu — esse checkpoint é a peça fulcral: a cadeia por si só é à prova de adulteração contra todos exceto a parte que opera o registador ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)). Quando a equipa de segurança de um cliente pergunta "o que fez o seu agente com os nossos dados?", você entrega-lhes um link em vez de um parágrafo. As revisões de segurança já fazem perguntas sobre IA ao lado da checklist SOC 2 — e cada vez mais essas perguntas vêm da ISO 42001, dos artigos de conservação de registos do Regulamento Europeu da IA e dos questionários dos próprios clientes. Hoje, uma garantia escrita ainda passa. A aposta por trás deste projeto é que não passará por muito tempo.
**Em destaque na [Help Net Security](https://www.helpnetsecurity.com/2026/08/31/halo-record-open-source-ai-agent-audit-trail/)** (agosto de 2026).
O formato de registo é aberto e livre de implementar. Este pacote é a implementação de referência: registador, verificador, cliente de testemunha e servidor de relatórios.
> **Está a usar o halo-record, ou a pensar nisso?** Diga-me quem é e para quê → [Quem está a usar o halo-record?](https://github.com/bkuan001/halo-record/discussions/7)
## Verifique você mesmo
Estão a pedir-lhe para colocar um registador dentro do seu agente. Não deve aceitar isso pela fé:
- **Zero dependências de runtime.** Apenas a biblioteca padrão. `pip install halo-record` instala exatamente um pacote.
- **Sem chamadas de rede**, exceto quatro opcionais — ancoragem a uma testemunha (envia o id do sujeito, uma contagem de registos e duas impressões digitais da cadeia — a cabeça e a raiz da cadeia), leitura dos checkpoints de uma testemunha (envia o id do sujeito), o timestamp RFC 3161 (envia apenas o hash de estado de um checkpoint a uma Autoridade de Timestamp) e `halo serve --verify-email` (envia um código de uso único e o nome do sujeito do relatório, por SMTP através do servidor que configurar em `$HALO_SMTP_HOST`, para o endereço que um visualizador do relatório introduzir). Todas estão desativadas a menos que as invoque; nenhum outro conteúdo de registo sai da sua infraestrutura.
- **Argumentos de ferramentas em bruto são transformados em hash, com um resumo redigido ao lado.** Os argumentos são armazenados como um hash canónico mais um resumo: o texto do argumento com padrões conhecidos de segredos e PII mascarados, limitado a 200 caracteres. Uma entrada curta que não corresponda a nenhum padrão aparece no resumo na íntegra; o modo apenas-hash (`summaries=False`) não guarda resumo algum. A redação é o melhor esforço possível (regex sobre formatos comuns de segredos e PII mais um apanha-tudo por entropia): trate-a como defesa em profundidade, não como garantia. Os campos de resultado que fornecer além de `summary` são analisados e mascarados com os mesmos padrões, mas não cortados a 200 caracteres (LIMITS §13).
- **Pequeno o suficiente para auditar.** ~8.700 linhas de Python, incluindo o JavaScript e CSS incorporados na página de relatório (linhas de código, sem contar linhas em branco, comentários e docstrings). Leia tudo num dia.
- **Apache-2.0.**
- **A documentação é de primeira classe.** [LIMITS.md](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md) (o que a cadeia não consegue provar), [PRIVACY.md](https://github.com/bkuan001/halo-record/blob/main/PRIVACY.md) (o que os registos contêm e o que sai da sua máquina), [RETENTION.md](https://github.com/bkuan001/halo-record/blob/main/RETENTION.md) (operar sob uma política de retenção) e [REVIEWERS.md](https://github.com/bkuan001/halo-record/blob/main/REVIEWERS.md) — a verificação independente em quatro comandos mais um formato de citação para resultados de revisão.
O que cada camada prova — a distinção fulcral neste projeto ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)): uma cadeia que você próprio detém prova que os registos não foram **editados**, relativamente a uma cabeça que alguém já detém; apenas checkpoints detidos fora do operador provam que nenhum foi **removido**; e nenhum hash prova que todas as ações foram **capturadas**.
| Afirmação | Cadeia própria | + Checkpoints externos | + Captura fiável |
|---|---|---|---|
| Detetar edições a um artefacto estabelecido | ✔ | ✔ | ✔ |
| Detetar reescrita de histórico comprometido | — | ✔ | ✔ |
| Detetar checkpoints em falta/atrasados | — | ✔ (cadência acordada) | ✔ |
| Provar que todas as ações foram registadas | — | — | depende do limite de captura |
**Veja um antes de instalar:** [um Runtime Report de exemplo](https://bkuan001.github.io/halo-record/sample-report.html) — dados fictícios, cadeia real, e o seu script recalcula a cadeia de hash dos registos incorporados no seu navegador quando o abre.
## Demonstração de 60 segundos
Não é necessário agente. Com [uv](https://docs.astral.sh/uv/), nada a instalar:
```
uvx --from halo-record halo demo --serve
```
ou da forma clássica:
```
pip install halo-record
halo demo --serve
```
Ou se cria um fornecedor fictício de agente de suporte com dois clientes, testemunha as cadeias (com um arquivo de testemunha local representando uma fora do operador — consulte [LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)), serve seus Runtime Reports com acesso restrito e abre o console do operador no seu navegador. Em seguida, tente o teste de adulteração: exclua uma linha de um dos arquivos `.jsonl` e recarregue. O relatório detecta isso.
## Registre seu próprio agente
Uma linha no limite:
```python
from halo_record import trace
agent = trace(run_my_agent, profile="my-agent", log="audit.jsonl") # wraps your entrypoint; records the run boundary to ./audit.jsonl — add record_call() or a framework adapter at each tool boundary to capture individual calls
```
Um shim de conveniência `from halo import ...` também é fornecido — mas o nome `halo` no PyPI pertence a um pacote de spinner de terminal não relacionado, e se esse pacote estiver instalado ele vence o import. `halo_record` é inequívoco, então os exemplos o utilizam.
Sem `log=`, os registros vão para `~/.halo/my-agent.jsonl` (uma cadeia por agente). O wrapper sela o limite da execução; a evidência vive nos registros por chamada. Capture-os com um adaptador de framework (matriz abaixo) — ou explicitamente, o que também mostra como a delegação se liga:
```python
from halo_record import Recorder, record_call
rec = Recorder("audit.jsonl")
with record_call(rec, "crm.lookup", {"account": "acct-9"}) as call: # one sealed record per tool call
call.result = crm.lookup("acct-9")
with record_call(rec, "payments.refund", {"amount": 120},
parent_id=rec.last_record_id()) as call: # child links to the action that spawned it
call.result = payments.refund(120)
```
Em seguida, renderize o relatório:
```
halo report audit.jsonl -o report.html # one chain -> HTML that re-verifies in the browser
halo serve ./records --port 8721 # all tenants, gated per customer
```
Atualizar um diretório servido pela primeira vez pela 0.2.45? O `halo serve` relê cada concessão anterior na inicialização. Uma concessão sobre um arquivo cujos registros não carregam um subject id (um arquivo `Recorder` simples, como `agent.jsonl`) é colocada em quarentena e impressa em cada inicialização com o comando `halo grant` que a reemite — consulte [LIMITS.md §17, "Serving a directory first served by 0.2.45"](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#17-one-chain-file-per-subject).
O quickstart termina quando você está olhando para o Runtime Report do seu próprio agente em um navegador. Se você obteve um arquivo JSONL e nenhum relatório, algo está errado: abra uma issue.
### O bloco de verificação
Se uma camada de guardrail ou de política verificou a ação, seu veredito pode acompanhar o registro — um bloco opcional que registra o que o gate decidiu, selado na cadeia de hash como qualquer outro campo:
```python
from halo_record import build
build("tool_call", "security", tool="payments.refund",
verification={"status": "allowed", "verifier": "gate/1.2",
"policy_ref": "sha256:1f3a...",
"checked_at": "2026-08-01T12:00:00Z"})
```
Uma observação sobre nomenclatura: o pacote exporta uma *função* `record` (o decorador), que oculta o *módulo* `halo_record.record` no objeto do pacote. Importe diretamente do caminho do módulo quando quiser seus internos — `from halo_record.record import build` — em vez de `import halo_record.record as record`.
que é selado no registro como:
```json
"verification": {"status": "allowed", "verifier": "gate/1.2", "policy_ref": "sha256:1f3a...", "checked_at": "2026-08-01T12:00:00Z"}
```
`record_call(...)` aceita a mesma palavra-chave `verification=`. `status` é obrigatório dentro do bloco; `verifier`, `policy_ref` e `checked_at` são opcionais. O que cada status significa:
| Status | O que o gate reporta | A ação foi executada? |
|---|---|---|
| `allowed` | permitiu a ação | sim — a ação prosseguiu |
| `blocked` | negou a ação | determinado pela integração, não por este campo — um registro ainda pode carregar um resultado, e um bloqueio por si só não prova a não execução |
| `modified` | alterou a ação antes da execução — `action.input` descreve a ação **como executada**, pós-modificação | sim, na forma alterada |
| `unverified` | foi executado (ou consultado) mas não fez nenhuma determinação — distinto de um bloco ausente, que significa que nenhuma alegação de verificação foi feita | sim — a ação prosseguiu sem um veredito |
O bloco é fornecido pelo código de integração do operador e registra o que ele reporta que o gate disse — a mesma postura de confiança que `principal` (veja [LIMITS](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#11-verification-status-is-the-gates-report-not-halos-finding)). A selagem prova que o status não foi editado após o fato; não prova que a verificação ocorreu, que o veredito estava correto, ou que uma ação bloqueada não foi executada. Isto não é verificação independente.
Para que `policy_ref` seja utilizável como evidência, use um hash de conteúdo do conjunto de regras e retenha o artefato do conjunto de regras — um rótulo não resolvível torna o campo decorativo.
## Conecte ao que você já executa
| Capturado na fronteira | Ingerido de telemetria existente |
|---|---|
| Gravador nativo (`from halo_record import trace`) | Spans OpenTelemetry GenAI |
| Interceptor MCP | Callbacks LiteLLM |
| Callback LangChain / LangGraph | Exportação Langfuse |
| Hooks do OpenAI Agents SDK | Qualquer log de gateway / proxy reverso |
| Hook do Claude Agent SDK | Hooks `PostToolUse` do Claude Code e Codex CLI (disparam após a ferramenta ser executada) |
Adaptadores de framework e caminhos de ingestão marcam cada registro com uma tag `source`, para que o relatório divulgue como cada peça de evidência foi coletada. Registros capturados e ingeridos vivem na mesma cadeia.
Para LangChain / LangGraph, é um manipulador de callback:
```python
from halo_record import Recorder
from halo_record.integrations.langchain import HaloCallbackHandler
recorder = Recorder("audit.jsonl")
result = my_chain.invoke(inputs, config={"callbacks": [HaloCallbackHandler(recorder)]}) # every tool call becomes a record
```
Para MCP, uma chamada envolve a sessão do cliente — e então *qualquer* agente que use MCP emite registros para cada chamada de ferramenta, independentemente de qual framework o conduz:
```python
from mcp import Client # mcp 2.x
from halo_record import Recorder
from halo_record.integrations.mcp import instrument_client_session
async def run(server): # an MCPServer, a server URL, or stdio parameters
async with Client(server) as client:
instrument_client_session(client, Recorder("audit.jsonl"), server="stripe")
await client.call_tool("charge", {"customer": "cus_123", "amount": 500}) # recorded
```
Cada `call_tool()` no cliente encapsulado é registado, e uma ferramenta que devolve um resultado de erro é selada como `error` (o adaptador lê tanto `isError` como o `is_error` do mcp 2.x). No mcp 1.x, passe o `ClientSession` inicializado em vez do `Client`; o wrapper é o mesmo.
Para logs de gateway ou proxy (Cloudflare AI Gateway, Portkey, nginx à frente do modelo), mapeie uma linha de log para a cadeia — explicitamente marcada como ingerida, não capturada na fronteira:
```python
from halo_record.integrations.gateway import record_log
record_log(Recorder("audit.jsonl"), {"tool": "gen_ai:gpt-4o", "model": "gpt-4o", "status": 200, "subject": "acme-corp"})
```
Qualquer coisa que emita spans OpenTelemetry GenAI (CrewAI, LlamaIndex e a maioria dos frameworks de agentes com instrumentação OTel) entra na cadeia através do adaptador OTel, e o [pacote TypeScript](https://github.com/bkuan001/halo-record-ts) fornece adaptadores nativos para o Vercel AI SDK e o ecossistema de agentes JS. Falta um adaptador para a sua stack? Abra uma issue. A maioria dos adaptadores tem cerca de cem linhas.
## Registre seu agente de codificação (Claude Code ou Codex)
O Claude Code dispara um hook `PostToolUse` após cada chamada de ferramenta. Aponte-o para `halo hook` e cada ação — gravações de arquivos, comandos de shell, chamadas de conectores MCP — torna-se um registro em uma cadeia local. Sem alterações de código; uma entrada de configuração:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": "*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
Adicione isso a `~/.claude/settings.json` e os registros chegam em `~/.halo/audit.jsonl` (substitua com `$HALO_LOG`). Ferramentas de pura orquestração que não tocam em dados, rede ou estado externo são ignoradas — a cadeia registra ações de limite de confiança, não pensamento. Defina `HALO_HASH_ONLY=1` para registrar hashes de conteúdo sem resumos. Defina `HALO_AGENT_VERSION` (e opcionalmente `HALO_AGENT_MODEL`) para vincular cada registro à build do agente que o produziu — quando um auditor perguntar sobre a versão que estava em execução em uma determinada janela, a exportação responde por coluna em vez de por recordação.
O Codex CLI traz os mesmos hooks de ciclo de vida com o mesmo formato de evento (os hooks vêm ativados por padrão). Adicione isto a `~/.codex/hooks.json` e os comandos de shell do Codex, as edições do `apply_patch` e as chamadas MCP chegam à mesma cadeia:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": ".*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
O hook distingue os dois a partir do próprio evento (o Codex adiciona `turn_id` e `model`) e rotula cada registro como `claude-code` ou `codex`; defina `HALO_HOOK_AGENT` para forçar um deles. Ambos são a camada ingerida: um hook `PostToolUse` dispara após a execução da ferramenta, portanto o registro é construído a partir do que o harness reporta.
Se você precisa que o relatório responda "sob quais regras esta execução ocorreu?", defina `HALO_AUTHORITY_FILE` como um snapshot JSON da autoridade efetiva para a sessão. Mantenha-o seguro em termos de privacidade: hashes e refs, não prompts brutos, texto de política privada, segredos ou esquemas completos de ferramentas — formatos conhecidos de segredos e dados pessoais são mascarados no momento do selo, mas hashes e refs passam intactos e texto de formato livre não é detectado (ver LIMITS §6). Reutilize um `snapshot_id` apenas enquanto a autoridade subjacente permanecer inalterada; registros consecutivos com o mesmo id e conteúdo inalterado são compactados — um id reutilizado sobre conteúdo alterado é armazenado por completo, com um aviso.
```json
{
"snapshot_id": "auth_2026_07_08T1100Z",
"captured_at": "2026-07-08T11:00:00Z",
"scope": "session",
"workspace": {"path_hash": "sha256:...", "git_commit": "abc1234"},
"refs": [
{"kind": "project_rules", "id": "CLAUDE.md", "hash": "sha256:...", "loaded": true, "truncated": false},
{"kind": "mcp_tool_registry", "id": "filesystem", "hash": "sha256:..."}
],
"omissions": [{"kind": "private_policy", "reason": "customer_secret", "hash": "sha256:..."}],
"stale_if": ["project_rules_hash_changed", "mcp_tool_registry_hash_changed"]
}
```
```sh
HALO_AUTHORITY_FILE=./authority.json halo hook
```
O snapshot é selado na mesma cadeia de hash que os registos de ação. Uma boa predefinição é um snapshot ao nível da sessão no início, mais um novo snapshot quando as regras, Skills, hooks, registos de ferramentas MCP ou política de compactação mudam. Para manter as sessões longas leves, registos consecutivos com o mesmo `authority.snapshot_id` são compactados após o primeiro snapshot completo: os registos posteriores mantêm apenas `{"snapshot_id": "...", "same_as_previous": true}`. O ponteiro permanece encadeado por hash, mas o bloco volumoso de refs/omissions/stale-if não é repetido em cada ação. (A compactação é por processo de registo: a captura ao estilo hook que gera um processo por chamada de ferramenta volta a armazenar o corpo completo sempre que o snapshot completo anterior não é o registo final, pelo que processos de curta duração trocam o tamanho da cadeia pela proteção de reutilização.)
Os utilizadores do SDK anexam o mesmo bloco diretamente — `build(..., authority={...})` ou `record_call(..., authority={...})`; a captura apenas de hash é a mesma superfície (`summaries=False` em qualquer um deles):
```python
from halo_record import Recorder, record_call
rec = Recorder("audit.jsonl")
with record_call(rec, "crm.lookup", {"account": "acct-9"},
authority={"snapshot_id": "auth_1", "rules_hash": "sha256:..."},
summaries=False) as call: # hash-only: no summaries, no excerpts
call.result = crm.lookup("acct-9")
```
Em seguida, o habitual:
```
halo verify ~/.halo/audit.jsonl
halo report ~/.halo/audit.jsonl -o report.html
```
Qualquer runtime de agente que exponha um hook pós-ação pode alimentar o mesmo comando — o hook lê um evento como JSON no stdin e acrescenta um registro.
Uma cadeia, um escritor de cada vez. Uma cadeia é uma lista ligada: dois escritores que leem a mesma cabeça e ambos acrescentam irão bifurcá-la (dois registros reivindicando o mesmo predecessor), e a verificação nomeará os registros afetados. O `Recorder` serializa os seus próprios acrescentos com um lock sidecar (POSIX `flock` aqui; um diretório de lock no pacote TypeScript), e o `halo hook` acrescenta através do `Recorder`, pelo que a configuração do hook acima está coberta. Qualquer coisa que escreva diretamente no ficheiro da cadeia — um hook artesanal, workers paralelos, um log shipper — deve manter um lock exclusivo equivalente ao longo da sequência de leitura-da-cabeça-e-depois-acrescento, ou escrever para cadeias por processo. [LIMITS.md secção 9](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#9-single-writer-chains) cobre isto na íntegra, incluindo a fronteira entre linguagens.
## Quando o registo falha
Os registos são escritos depois de a ação ser executada. O Halo não bloqueia uma ação quando o seu registo não pode ser escrito; escrever o registo antes da ação não é oferecido.
- **Predefinição: o resultado da ação passa inalterado.** Se um registo não puder ser escrito (disco cheio, permissões, um timeout de lock), `trace()`, `record_call`, `@record`, os adaptadores de framework (OpenAI Agents, LangChain, LiteLLM, OpenTelemetry), o wrapper MCP e `halo hook` devolvem o próprio resultado da ação inalterado, ou relançam a própria exceção da ação se esta falhou. Um aviso no stderr nomeia o registo em falta: `halo-record: FAILED to record '<tool>' (<error>); the action ran, but its record is NOT in the evidence log. ...` (sem stderr utilizável, vai para o logger `halo_record`). O handler LangChain e o exportador OTel também contam perdas (`lost_records`). Nada na cadeia pode mostrar um registo que nunca foi escrito, pelo que os restantes registos ainda verificam. Um revisor que observa a cadência do checkpoint testemunha vê uma cadeia que estagnou; um único registo em falta entre registos posteriores mostra-se apenas na linha de stderr.
- **O modo estrito também lança.** Passe `strict_recording=True` (para `trace`, `record_call`, `@record`, `instrument_client_session`, ou o construtor de um adaptador), ou defina `HALO_STRICT_RECORDING=1`, e a mesma linha de stderr é escrita, e depois o erro de registo é lançado. Chega ao chamador a partir de `trace`, `record_call`, `@record`, do wrapper MCP, dos hooks OpenAI Agents e do handler LangChain (que define o `raise_error` do LangChain). Não consegue chegar ao chamador a partir do exportador OTel ou do logger LiteLLM: esses frameworks capturam e registam o que um exportador ou callback lança, pelo que aí a linha de stderr é o sinal em ambos os modos (LIMITS.md secção 18). A ação já foi executada quando o erro é lançado, pelo que um chamador que o trate como "a ação falhou" e tente novamente irá repetir os efeitos secundários da ação (um reembolso emitido duas vezes). Use o modo estrito apenas onde um registo em falta é pior do que esse risco.
- `record_call` e `@record` sem recorder (nenhum passado, nenhum vinculado por `trace`) lançam `RuntimeError` antes de o código envolvido ser executado.
- `@record` e `trace()` aceitam funções `async def`. O wrapper é ele próprio `async def`: aguarda a função, depois regista o que esta devolveu ou lançou (um cancelamento é registado como erro). `trace()` mantém o seu recorder vinculado enquanto o corpo é aguardado, pelo que uma ferramenta `record_call` ou `@record` dentro de um agente assíncrono aterra na cadeia rastreada, e agentes concorrentes mantêm cada um a sua. Uma função simples que devolve um awaitable é registada quando retorna. Uma função geradora assíncrona é registada quando é chamada, com o objeto gerador como seu resultado; o que ela produz não é registado.
## Integridade vs. completude (leia esta parte)
Seja preciso sobre o que cada camada prova — porque são afirmações diferentes, e as diferenças são o ponto:
Uma cadeia auto-mantida prova **integridade relativa a uma cabeça estabelecida**: dada uma cabeça de cadeia que alguém já detém, qualquer edição, reordenação ou eliminação nos registos por trás dela torna-se detetável. Por si só — antes de alguém fora do operador ter visto uma cabeça — uma cadeia prova consistência interna, não histórico: um operador poderia eliminar um registo e re-selar, e o novo ficheiro verificaria. A cadeia torna-se **historicamente comprometida** no momento em que a sua cabeça sai do controlo do operador.
Esse é o witness: uma parte fora do operador que detém checkpoints periódicos da cadeia — o id do sujeito, uma contagem de registos, e duas impressões digitais da cadeia (a cabeça e a raiz da cadeia); o payload exato, e nada mais. Os checkpoints tornam detetável a reescrita de histórico comprometido, e um checkpoint falhado é ele próprio um evento visível:
```
halo anchor audit.jsonl witness.jsonl # anchor a checkpoint to a local witness
halo anchor audit.jsonl witness.jsonl --check # completeness verdict against it
```
Para *time* especificamente, um timestamp RFC 3161 externo substitui o relógio autodeclarado do checkpoint por uma prova de uma Timestamp Authority fora do controle do operador — "esta cadeia atingiu este head não depois de T", verificável por um terceiro sem infraestrutura hospedada. A TSA padrão é a freetsa.org gratuita (adequada para avaliação); para produção, aponte `--tsa` para uma TSA fora do controle do operador (por exemplo, DigiCert / Sectigo, ou uma designada por uma parte confiável):
```
halo anchor audit.jsonl witness.jsonl --timestamp # attach a TSA time proof to the checkpoint
halo anchor audit.jsonl witness.jsonl --check # reads the token's claimed time
```
`--check` confirma que o token vincula este estado da cadeia e lê o seu tempo atestado, mas **não** valida a assinatura da TSA — isso é deliberadamente deixado para uma ferramenta padrão, para que um revisor não precise confiar em nenhum código nosso. Para verificar o tempo de forma independente (isto é o que você entrega a um revisor de segurança):
```
# tsa.token_b64 lives in the witness log; decode the latest one to a standard .tsr file
python3 -c 'import json,base64; cps=[json.loads(l) for l in open("witness.jsonl") if l.strip()]; t=[c["tsa"] for c in cps if c.get("tsa")][-1]; open("token.tsr","wb").write(base64.b64decode(t["token_b64"])); print(t["digest"])'
curl -s -o tsa-ca.pem https://freetsa.org/files/cacert.pem # CA for the default TSA (a commercial TSA publishes its own)
openssl ts -verify -digest <the digest printed above> -in token.tsr -CAfile tsa-ca.pem # → "Verification: OK"
```
Mais um limite, dito de forma clara: nem a cadeia nem a testemunha provam que toda ação do mundo real passou pelo gravador. Isso é **completude de captura** — uma propriedade de onde o gravador se posiciona na pilha (instrumentação nativa, hooks, ingestão via gateway), não de qualquer hash. Os registros carregam uma tag `source` exatamente por esse motivo. A tabela de afirmações em "Verifique você mesmo" no topo desta página é o resumo dessas três camadas.
Qualquer pessoa pode executar uma testemunha. Uma testemunha que você mesmo executa compromete o histórico a *você*; comprometê-lo ao *seu cliente* exige uma testemunha na qual ele tenha motivos para confiar. O protocolo é aberto de qualquer forma.
Uma testemunha hospedada e reconhecida é como este projeto se sustentará. Acesso antecipado: [email protected].
## Dados pessoais na cadeia
A cadeia é somente-anexo: qualquer coisa selada em um registro permanece lá, porque
removê-la quebraria a verificação de tudo que vem depois. Os argumentos de ferramenta já
são tratados — armazenados como um hash mais um resumo redigido limitado a 200 caracteres
(o modo somente-hash não mantém resumo; em campos id ele mascara um token em um formato
de segredo conhecido como nos resumos e substitui qualquer outro token com forma de segredo por `sha256:`
mais 16 dígitos hexadecimais — todos exceto `subject.id`, que encaminha o registro para a cadeia do seu tenant).
Observe o limite nessa frase: *redigido*, não removido. A seção 6 do [LIMITS.md](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)
é explícita ao afirmar que um nome ou um endereço postal não têm padrão confiável, então
nenhum dos dois é detectado e nenhum dos dois é mascarado. E `subject` não é o único campo que
carrega texto que você fornece — `principal`, `approver`, `session_id`, `agent`,
`authority`, `data` e os resumos também carregam.
O padrão que funciona: coloque um id pseudônimo estável na cadeia e mantenha o
mapeamento para qualquer indivíduo em um sistema do qual você possa excluir. Uma solicitação de apagamento é
então atendida excluindo o mapeamento. Mantenha `subject` apontando para a organização tenant, não para uma pessoa:
```python
from halo_record import build
build("tool_call", "privacy", subject={"id": "acme", "name": "Acme Corp"})
```
Nenhuma configuração impõe isto — é uma disciplina na forma como chamas o gravador.
Torna o apagamento tratável; não é anonimização, e ainda não existe retenção
ou limpeza integradas.
[LIMITS.md secção 13](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#13-personal-data-and-erasure) tem a lista completa de campos,
explica porque é que a impressão digital do input armazenada pode confirmar um valor adivinhável mesmo
depois de o mapeamento desaparecer, e termina com perguntas que um revisor deve fazer.
Registar uma chamada a um modelo (a primeira pergunta do comprador: "que modelo viu os meus dados?"):
```python
from halo_record import Recorder, record_model_call
rec = Recorder("audit.jsonl")
record_model_call(rec, provider="anthropic", model="claude-sonnet-4-6",
zdr=True, purpose="draft support reply",
subject="acme") # tool=model.generate, scope=model:anthropic
```
O modelo também é selado como `agent.model` e os termos de retenção como `data.zdr`,
para que a coluna de modelo e as regras de política da exportação possam lê-los, e ambos sobrevivem
a `summaries=False`. Passe `messages=` e a entrada do registro carrega quantas havia
— uma contagem, nunca o seu conteúdo.
## Onde isto se situa numa stack de conformidade
halo-record é uma camada de evidência, não uma certificação. Produz o artefacto que os quadros de avaliação continuam a pedir por outras palavras. Uma nota de âmbito que rege cada ponto abaixo: estas são afirmações de integridade sobre o registro; a completude face ao operador exige uma testemunha externa que detenha checkpoints ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)).
- **Questionários de segurança e revisões SOC 2:** responda às secções de IA com um Runtime Report verificável em vez de capturas de ecrã e prosa.
- **AIUC-1:** produz a evidência de logging à prova de adulteração (E015.4) e registos de cadeia de execução com eventos de autorização (E015.2 — com uma lacuna declarada: os traços de raciocínio não são capturados) que o controlo de Accountability E015 da norma nomeia. O próprio E015 é obrigatório; E015.2 e E015.4 são o seu nível suplementar: não exigidos para passar, aquele que um fornecedor adota quando um cliente ou regulador o pede. Assim que a cadeia é ancorada a uma testemunha em que a parte confiante tem razões para confiar — uma testemunha que o próprio operador executa não fornece isto — isso é uma cadeia continuamente testemunhada em vez de uma reconstruída no momento da auditoria (o que entrou na cadeia continua limitado pela superfície de captura). Um mapeamento de evidência controlo a controlo, incluindo o que está deliberadamente fora de âmbito, está em [`AIUC.md`](https://github.com/bkuan001/halo-record/blob/main/AIUC.md).
- **OWASP Top 10 for Agentic Applications 2026:** oito das dez ameaças mapeiam para regras de política determinísticas sobre o registro, duas estão marcadas como fora de âmbito com justificações, e o pacote vem incluído no pacote, executável por nome (`halo policy chain.jsonl owasp-starter`). Um mapeamento comunitário aproximado, não um artefacto oficial da OWASP. Ver [`OWASP.md`](https://github.com/bkuan001/halo-record/blob/main/OWASP.md).
- **AARM (CSA):** produz o recibo de ação à prova de adulteração que a AARM especifica — R5, e a metade de selagem de R6 (a identidade é selada no hash, não autenticada criptograficamente). halo-record é a camada de recibos; emparelhe-o com um gateway de enforcement para um sistema AARM completo. Ver [`AARM.md`](https://github.com/bkuan001/halo-record/blob/main/AARM.md).
---
[Read more](https://github.com/bkuan001/halo-record)