
Registri di controllo a prova di manomissione per agenti AI: Runtime Records concatenati tramite hash, senza dipendenze, verificabili da chiunque.
# halo-record
**Audit trail a prova di manomissione per agenti AI** — Runtime Record concatenati tramite hash, visualizzati come Runtime Report che i tuoi clienti possono verificare autonomamente.
Ogni azione catturata dalla tua strumentazione (chiamate a tool, chiamate a modelli, accesso ai dati, approvazioni) diventa un **Runtime Record** in un log append-only concatenato tramite hash; il **Runtime Report** è quella catena visualizzata come pagina HTML che ricalcola la catena di hash nel browser del lettore e visualizza ciò che mostra a partire dai record che ha verificato (per una verifica che non dipende dal codice della pagina stessa, esegui `halo verify` sul JSONL). Qualsiasi parte in possesso di un checkpoint della catena può verificare che i record fino a quel checkpoint non siano stati alterati da quando è stato acquisito, senza fidarsi di chi li ha prodotti — quel checkpoint è il pezzo portante: la catena da sola è a prova di manomissione contro chiunque tranne la parte che gestisce il recorder ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)). Quando il team di sicurezza di un cliente chiede "cosa ha fatto il tuo agente con i nostri dati?", gli consegni un link invece di un paragrafo. Le revisioni di sicurezza pongono già domande sull'AI accanto alla checklist SOC 2 — e sempre più spesso quelle domande provengono da ISO 42001, dagli articoli sulla conservazione dei registri dell'EU AI Act e dai questionari dei clienti stessi. Oggi un'assicurazione scritta passa ancora. La scommessa dietro questo progetto è che non durerà a lungo.
**In evidenza su [Help Net Security](https://www.helpnetsecurity.com/2026/08/31/halo-record-open-source-ai-agent-audit-trail/)** (agosto 2026).
Il formato dei record è aperto e libero da implementare. Questo pacchetto è l'implementazione di riferimento: recorder, verifier, client witness e server dei report.
> **Stai usando halo-record, o ci stai pensando?** Dimmi chi sei e per cosa → [Chi sta usando halo-record?](https://github.com/bkuan001/halo-record/discussions/7)
## Verificalo tu stesso
Ti viene chiesto di inserire un recorder dentro il tuo agente. Non dovresti accettarlo sulla fiducia:
- **Zero dipendenze a runtime.** Solo libreria standard. `pip install halo-record` installa esattamente un pacchetto.
- **Nessuna chiamata di rete**, tranne quattro opt-in — l'ancoraggio a un witness (invia l'id del soggetto, un conteggio dei record e due impronte della catena — la testa e la radice della catena), la lettura dei checkpoint di un witness (invia l'id del soggetto), il timestamp RFC 3161 (invia solo l'hash di stato di un checkpoint a una Timestamp Authority) e `halo serve --verify-email` (invia un codice monouso e il nome del soggetto del report, via SMTP attraverso il server che configuri in `$HALO_SMTP_HOST`, all'indirizzo inserito da chi visualizza il report). Tutte sono disattivate a meno che tu non le invochi; nessun altro contenuto dei record lascia la tua infrastruttura.
- **Gli argomenti grezzi dei tool vengono hashati, con accanto un riepilogo redatto.** Gli argomenti sono memorizzati come hash canonico più un riepilogo: il testo dell'argomento con i pattern noti di segreti e PII mascherati, limitato a 200 caratteri. Un input breve che non corrisponde ad alcun pattern appare per intero nel riepilogo; la modalità solo-hash (`summaries=False`) non conserva alcun riepilogo. La redazione è best-effort (regex sui formati comuni di segreti e PII più un catch-all basato sull'entropia): trattala come difesa in profondità, non come garanzia. I campi di esito che fornisci oltre a `summary` vengono scansionati e mascherati con gli stessi pattern, ma non troncati a 200 caratteri (LIMITS §13).
- **Abbastanza piccolo da poter essere verificato.** ~8.700 righe di Python, incluso il JavaScript e il CSS incorporati nella pagina del report (righe di codice, escludendo righe vuote, commenti e docstring). Puoi leggerlo tutto in un giorno.
- **Apache-2.0.**
- **La documentazione è di prim'ordine.** [LIMITS.md](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md) (cosa la catena non può dimostrare), [PRIVACY.md](https://github.com/bkuan001/halo-record/blob/main/PRIVACY.md) (cosa contengono i record e cosa lascia la tua macchina), [RETENTION.md](https://github.com/bkuan001/halo-record/blob/main/RETENTION.md) (operare sotto una policy di conservazione) e [REVIEWERS.md](https://github.com/bkuan001/halo-record/blob/main/REVIEWERS.md) — la verifica indipendente in quattro comandi più un formato di citazione per i risultati della revisione.
Cosa dimostra ciascun livello — la distinzione portante in questo progetto ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)): una catena che possiedi tu stesso dimostra che i record non sono stati **modificati**, rispetto a una testa che qualcun altro già possiede; solo i checkpoint conservati al di fuori dell'operatore dimostrano che nessuno è stato **rimosso**; e nessun hash dimostra che ogni azione sia stata **catturata**.
| Affermazione | Catena auto-conservata | + Checkpoint esterni | + Cattura attendibile |
|---|---|---|---|
| Rilevare modifiche a un artefatto consolidato | ✔ | ✔ | ✔ |
| Rilevare la riscrittura della cronologia consolidata | — | ✔ | ✔ |
| Rilevare checkpoint mancanti/in ritardo | — | ✔ (cadenza concordata) | ✔ |
| Dimostrare che ogni azione è stata registrata | — | — | dipende dal confine di cattura |
**Guardane uno prima di installare:** [un Runtime Report di esempio](https://bkuan001.github.io/halo-record/sample-report.html) — dati fittizi, catena reale, e il suo script ricalcola nel tuo browser la catena di hash dei record incorporati quando lo apri.
## Demo in 60 secondi
Nessun agente richiesto. Con [uv](https://docs.astral.sh/uv/), niente da installare:
```
uvx --from halo-record halo demo --serve
```
o nel modo classico:
```
pip install halo-record
halo demo --serve
```
Either one scaffolds a fictional support-agent vendor with two customers, witnesses the chains (with a local witness file standing in for one outside the operator — see [LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)), serves their gated Runtime Reports, and opens the operator console in your browser. Then try the tamper test: delete a line from one of the `.jsonl` files and reload. The report catches it.
## Registra il tuo agente
Una riga al confine:
```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
```
Un `from halo import ...` convenience shim viene fornito anch'esso — ma il nome `halo` su PyPI appartiene a un pacchetto terminal-spinner non correlato, e se quel pacchetto è installato vince l'import. `halo_record` è inequivocabile, quindi gli esempi lo usano.
Senza `log=`, i record vanno in `~/.halo/my-agent.jsonl` (una catena per agente). Il wrapper sigilla il confine dell'esecuzione; l'evidenza risiede nei record per chiamata. Catturali con un adattatore di framework (matrice sotto) — o esplicitamente, il che mostra anche come si collegano le deleghe:
```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)
```
Poi genera il report:
```
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
```
L'aggiornamento di una directory servita per la prima volta dalla 0.2.45? `halo serve` rilegge ogni concessione precedente all'avvio. Una concessione su un file i cui record non portano alcun subject id (un semplice file `Recorder` come `agent.jsonl`) viene messa in quarantena e stampata a ogni avvio con il comando `halo grant` che la riemette — vedi [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).
La quickstart termina quando stai guardando il Runtime Report del tuo agente in un browser. Se hai ottenuto un file JSONL e nessun report, qualcosa non va: apri una issue.
### Il blocco di verifica
Se un guardrail o uno strato di policy ha controllato l'azione, il suo verdetto può viaggiare sul record — un blocco opzionale che registra ciò che il gate ha deciso, sigillato nella catena di hash come ogni altro 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"})
```
Una nota sulla denominazione: il pacchetto esporta una *funzione* `record` (il decoratore), che oscura il *modulo* `halo_record.record` sull'oggetto del pacchetto. Importa direttamente dal percorso del modulo quando vuoi i suoi internals — `from halo_record.record import build` — invece di `import halo_record.record as record`.
che si sigilla nel record come:
```json
"verification": {"status": "allowed", "verifier": "gate/1.2", "policy_ref": "sha256:1f3a...", "checked_at": "2026-08-01T12:00:00Z"}
```
`record_call(...)` accetta la stessa keyword `verification=`. `status` è obbligatorio all'interno del blocco; `verifier`, `policy_ref` e `checked_at` sono opzionali. Cosa significa ogni stato:
| Stato | Cosa riporta il gate | L'azione è stata eseguita? |
|---|---|---|
| `allowed` | ha permesso l'azione | sì — l'azione è proceduta |
| `blocked` | ha negato l'azione | determinato dall'integrazione, non da questo campo — un record può comunque riportare un esito, e un blocco di per sé non prova la mancata esecuzione |
| `modified` | ha alterato l'azione prima dell'esecuzione — `action.input` descrive l'azione **così come eseguita**, dopo la modifica | sì, nella forma alterata |
| `unverified` | è stato eseguito (o consultato) ma non ha prodotto alcuna determinazione — distinto da un blocco assente, che significa che non è stata avanzata alcuna dichiarazione di verifica | sì — l'azione è proceduta senza verdetto |
Il blocco è fornito dal codice di integrazione dell'operatore e registra ciò che esso riporta come detto dal gate — la stessa postura di fiducia di `principal` (vedi [LIMITS](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#11-verification-status-is-the-gates-report-not-halos-finding)). La sigillatura prova che lo stato non è stato modificato a posteriori; non prova che il controllo sia avvenuto, che il verdetto fosse corretto, o che un'azione bloccata non sia stata eseguita. Questa non è una verifica indipendente.
Affinché `policy_ref` sia utilizzabile come prova, usa un hash del contenuto del ruleset e conserva l'artefatto del ruleset — un'etichetta non risolvibile rende il campo decorativo.
## Connettiti a ciò che già esegui
| Catturato al confine | Acquisito dalla telemetria esistente |
|---|---|
| Recorder nativo (`from halo_record import trace`) | Span OpenTelemetry GenAI |
| Intercettore MCP | Callback LiteLLM |
| Callback LangChain / LangGraph | Esportazione Langfuse |
| Hook OpenAI Agents SDK | Qualsiasi log di gateway / reverse-proxy |
| Hook Claude Agent SDK | Hook `PostToolUse` di Claude Code e Codex CLI (si attivano dopo l'esecuzione dello strumento) |
Gli adattatori di framework e i percorsi di acquisizione contrassegnano ogni record con un tag `source`, così il report divulga come è stata raccolta ogni prova. I record catturati e quelli acquisiti risiedono nella stessa catena.
Per LangChain / LangGraph, è un callback handler:
```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
```
Per MCP, una singola chiamata racchiude la sessione client — e poi *qualsiasi* agente che utilizza MCP emette record per ogni chiamata di strumento, indipendentemente dal framework che lo guida:
```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
```
Ogni `call_tool()` sul client wrappato viene registrato, e un tool che restituisce un risultato di errore viene sigillato come `error` (l'adapter legge sia `isError` che `is_error` di mcp 2.x). Su mcp 1.x, passare la `ClientSession` inizializzata invece di `Client`; il wrapper è lo stesso.
Per i log di gateway o proxy (Cloudflare AI Gateway, Portkey, nginx davanti al modello), mappare una riga di log nella catena — esplicitamente contrassegnata come acquisita, non catturata al confine:
```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"})
```
Qualsiasi cosa che emetta span OpenTelemetry GenAI (CrewAI, LlamaIndex e la maggior parte dei framework di agenti con strumentazione OTel) entra nella catena attraverso l'adattatore OTel, e il [pacchetto TypeScript](https://github.com/bkuan001/halo-record-ts) fornisce adattatori nativi per il Vercel AI SDK e l'ecosistema degli agenti JS. Manca un adattatore per il tuo stack? Apri una issue. La maggior parte degli adattatori è di circa un centinaio di righe.
## Registra il tuo agente di coding (Claude Code o Codex)
Claude Code attiva un hook `PostToolUse` dopo ogni chiamata a uno strumento. Puntalo su `halo hook` e ogni azione — scritture di file, comandi shell, chiamate a connettori MCP — diventa un record in una catena locale. Nessuna modifica al codice; una voce nelle impostazioni:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": "*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
Aggiungilo a `~/.claude/settings.json` e i record finiscono in `~/.halo/audit.jsonl` (sovrascrivibile con `$HALO_LOG`). Gli strumenti di pura orchestrazione che non toccano dati, rete o stato esterno vengono saltati — la catena registra le azioni sui confini di fiducia, non il pensiero. Imposta `HALO_HASH_ONLY=1` per registrare gli hash dei contenuti senza riepiloghi. Imposta `HALO_AGENT_VERSION` (e facoltativamente `HALO_AGENT_MODEL`) per vincolare ogni record alla build dell'agente che l'ha prodotto — quando un revisore chiede informazioni sulla versione in esecuzione in una determinata finestra, l'esportazione risponde per colonna invece che a memoria.
Codex CLI fornisce gli stessi hook del ciclo di vita con la stessa forma degli eventi (gli hook sono attivi per impostazione predefinita). Aggiungi questo a `~/.codex/hooks.json` e i comandi shell di Codex, le modifiche di `apply_patch` e le chiamate MCP finiscono nella stessa catena:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": ".*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
L'hook distingue i due dall'evento stesso (Codex aggiunge `turn_id` e `model`) ed etichetta ogni record come `claude-code` o `codex`; imposta `HALO_HOOK_AGENT` per forzarne uno. Entrambi appartengono al tier ingerito: un hook `PostToolUse` viene attivato dopo l'esecuzione dello strumento, quindi il record viene costruito da ciò che l'harness riporta.
Se hai bisogno che il report risponda alla domanda "sotto quali regole è avvenuta questa esecuzione?", imposta `HALO_AUTHORITY_FILE` su uno snapshot JSON dell'autorità effettiva per la sessione. Mantienilo sicuro per la privacy: hash e ref, non prompt grezzi, testo di policy privato, segreti o schemi di strumenti completi — i formati noti di segreti e dati personali vengono mascherati al momento del seal, ma hash e ref passano inalterati e il testo libero non viene rilevato (vedi LIMITS §6). Riusa uno `snapshot_id` solo finché l'autorità sottostante rimane invariata; record consecutivi con lo stesso id e contenuto invariato vengono compattati — un id riutilizzato su contenuto modificato viene memorizzato per intero, con un avviso.
```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
```
Lo snapshot è sigillato nella stessa catena di hash dei record delle azioni. Un buon valore predefinito è uno snapshot a livello di sessione all'avvio, più un nuovo snapshot quando cambiano regole, Skills, hook, registri di strumenti MCP o policy di compattazione. Per mantenere snelle le sessioni lunghe, i record consecutivi con lo stesso `authority.snapshot_id` vengono compattati dopo il primo snapshot completo: i record successivi mantengono solo `{"snapshot_id": "...", "same_as_previous": true}`. Il puntatore rimane concatenato tramite hash, ma il blocco voluminoso refs/omissions/stale-if non viene ripetuto a ogni azione. (La compattazione è per processo di registrazione: la cattura in stile hook che genera un processo per ogni chiamata di strumento ri-archivia il corpo completo ogni volta che lo snapshot completo precedente non è il record di coda, quindi i processi di breve durata scambiano la dimensione della catena con la protezione di riutilizzo.)
Gli utenti dell'SDK allegano lo stesso blocco direttamente — `build(..., authority={...})` o `record_call(..., authority={...})`; la cattura solo-hash è la stessa superficie (`summaries=False` su uno qualsiasi di essi):
```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")
```
Poi, il solito:
```
halo verify ~/.halo/audit.jsonl
halo report ~/.halo/audit.jsonl -o report.html
```
Qualsiasi runtime di agent che espone un hook post-azione può alimentare lo stesso comando — l'hook legge un evento come JSON su stdin e aggiunge un record.
Una catena, un solo scrittore alla volta. Una catena è una lista concatenata: due scrittori che leggono la stessa testa e aggiungono entrambi la faranno divergere (due record che rivendicano lo stesso predecessore), e la verifica nominerà i record interessati. `Recorder` serializza i propri append con un lock sidecar (POSIX `flock` qui; una directory di lock nel pacchetto TypeScript), e `halo hook` aggiunge tramite `Recorder`, quindi la configurazione dell'hook sopra è coperta. Qualsiasi cosa che scriva direttamente il file della catena — un hook scritto a mano, worker paralleli, un log shipper — deve mantenere un lock esclusivo equivalente attraverso la sequenza leggi-testa-poi-aggiungi, oppure scrivere su catene per-processo. [LIMITS.md sezione 9](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#9-single-writer-chains) copre questo in dettaglio, incluso il confine cross-linguaggio.
## Quando la registrazione fallisce
I record vengono scritti dopo l'esecuzione dell'azione. Halo non blocca un'azione quando il suo record non può essere scritto; scrivere il record prima dell'azione non è offerto.
- **Predefinito: il risultato dell'azione passa attraverso.** Se un record non può essere scritto (disco pieno, permessi, un timeout di lock), `trace()`, `record_call`, `@record`, gli adattatori di framework (OpenAI Agents, LangChain, LiteLLM, OpenTelemetry), il wrapper MCP e `halo hook` restituiscono il risultato proprio dell'azione invariato, oppure rilanciano l'eccezione propria dell'azione se questa è fallita. Un avviso su stderr nomina il record mancante: `halo-record: FAILED to record '<tool>' (<error>); the action ran, but its record is NOT in the evidence log. ...` (senza uno stderr utilizzabile, va al logger `halo_record`). Il gestore LangChain e l'esportatore OTel contano anche le perdite (`lost_records`). Nulla nella catena può mostrare un record che non è mai stato scritto, quindi i record rimanenti verificano comunque. Un revisore che osserva la cadenza del checkpoint witness vede una catena che si è bloccata; un singolo record mancante tra quelli successivi si mostra solo nella riga di stderr.
- **La modalità strict solleva anch'essa.** Passa `strict_recording=True` (a `trace`, `record_call`, `@record`, `instrument_client_session`, o al costruttore di un adattatore), oppure imposta `HALO_STRICT_RECORDING=1`, e la stessa riga di stderr viene scritta, poi l'errore di registrazione viene sollevato. Raggiunge il chiamante da `trace`, `record_call`, `@record`, il wrapper MCP, gli hook OpenAI Agents e il gestore LangChain (che imposta `raise_error` di LangChain). Non può raggiungere il chiamante dall'esportatore OTel o dal logger LiteLLM: quei framework catturano e registrano ciò che un esportatore o una callback solleva, quindi lì la riga di stderr è il segnale in entrambe le modalità (LIMITS.md sezione 18). L'azione è già stata eseguita quando l'errore viene sollevato, quindi un chiamante che lo interpreta come "l'azione è fallita" e riprova ripeterà gli effetti collaterali dell'azione (un rimborso emesso due volte). Usa la modalità strict solo dove un record mancante è peggio di quel rischio.
- `record_call` e `@record` senza un recorder (nessuno passato, nessuno associato da `trace`) sollevano `RuntimeError` prima che il codice avvolto venga eseguito.
- `@record` e `trace()` accettano funzioni `async def`. Il wrapper è esso stesso `async def`: attende la funzione, poi registra ciò che ha restituito o sollevato (una cancellazione viene registrata come errore). `trace()` mantiene il suo recorder associato mentre il corpo viene atteso, quindi un tool `record_call` o `@record` dentro un agent asincrono atterra sulla catena tracciata, e agent concorrenti mantengono ciascuno il proprio. Una funzione semplice che restituisce un awaitable viene registrata quando ritorna. Una funzione generatore asincrona viene registrata quando viene chiamata, con l'oggetto generatore come suo risultato; ciò che produce non viene registrato.
## Integrità vs. completezza (leggi questa parte)
Sii preciso su ciò che ogni livello dimostra — perché sono affermazioni diverse, e le differenze sono il punto:
Una catena auto-detentua dimostra **integrità rispetto a una testa stabilita**: data una testa di catena che qualcuno già possiede, qualsiasi modifica, riordino o cancellazione nei record dietro di essa diventa rilevabile. Di per sé — prima che qualcuno al di fuori dell'operatore abbia visto una testa — una catena dimostra coerenza interna, non storia: un operatore potrebbe eliminare un record e ri-sigillare, e il nuovo file verificherebbe. La catena diventa **storicamente impegnata** nel momento in cui la sua testa lascia il controllo dell'operatore.
Questo è il witness: una parte al di fuori dell'operatore che detiene checkpoint periodici della catena — l'id del soggetto, un conteggio dei record, e due impronte della catena (la testa e la radice della catena); il payload esatto, e nulla di più. I checkpoint rendono rilevabile la riscrittura della storia impegnata, e un checkpoint mancato è esso stesso un evento visibile:
```
halo anchor audit.jsonl witness.jsonl # anchor a checkpoint to a local witness
halo anchor audit.jsonl witness.jsonl --check # completeness verdict against it
```
Per *time* nello specifico, un timestamp RFC 3161 esterno sostituisce l'orologio auto-dichiarato del checkpoint con una prova fornita da una Timestamp Authority al di fuori del controllo dell'operatore — "questa catena ha raggiunto questa testa non oltre T", verificabile da una terza parte senza alcuna infrastruttura ospitata. La TSA predefinita è la gratuita freetsa.org (adatta per la valutazione); per la produzione, puntare `--tsa` a una TSA al di fuori del controllo dell'operatore (ad es. DigiCert / Sectigo, o una designata da una relying party):
```
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` conferma che il token è vincolato a questo stato della catena e ne legge il tempo attestato, ma **non** convalida la firma della TSA — ciò è deliberatamente lasciato a uno strumento standard affinché un revisore non debba fidarsi di alcun nostro codice. Per verificare il tempo in modo indipendente (questo è ciò che si consegna a un revisore di sicurezza):
```
# 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"
```
Un altro confine, detto chiaramente: né la catena né il witness dimostrano che ogni azione nel mondo reale sia passata attraverso il recorder. Questa è la **completezza di cattura** — una proprietà di dove si colloca il recorder nello stack (strumentazione nativa, hook, ingestione tramite gateway), non di un hash qualsiasi. I record portano un tag `source` esattamente per questo motivo. La tabella delle affermazioni sotto "Check it yourself" in cima a questa pagina è il riepilogo di questi tre livelli.
Chiunque può eseguire un witness. Un witness che esegui tu stesso impegna la cronologia verso *di te*; impegnarla verso *il tuo cliente* richiede un witness di cui abbia motivo di fidarsi. Il protocollo è aperto in entrambi i casi.
Un witness ospitato e riconosciuto è il modo in cui questo progetto si sosterrà. Accesso anticipato: [email protected].
## Dati personali nella catena
La catena è append-only: qualsiasi cosa venga sigillata in un record rimane lì, perché
rimuoverla romperebbe la verifica per tutto ciò che segue. Gli argomenti degli strumenti sono
già gestiti — memorizzati come un hash più un riepilogo redatto limitato a 200 caratteri
(la modalità solo-hash non conserva alcun riepilogo; nei campi id maschera un token in un formato
segreto noto come nei riepiloghi e sostituisce qualsiasi altro token dall'aspetto segreto con `sha256:`
più 16 cifre esadecimali — tutti tranne `subject.id`, che instrada il record verso la catena del suo tenant).
Nota il limite in quella frase: *redatto*, non rimosso. [LIMITS.md](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)
la sezione 6 è esplicita sul fatto che un nome o un indirizzo postale non hanno un pattern affidabile, quindi
nessuno dei due viene rilevato e nessuno dei due viene mascherato. E `subject` non è l'unico campo che
contiene testo da te fornito — `principal`, `approver`, `session_id`, `agent`,
`authority`, `data` e i riepiloghi lo fanno tutti.
Il pattern che funziona: inserisci un id pseudonimo stabile nella catena e conserva la
mappatura verso qualsiasi individuo in un sistema da cui puoi cancellare. Una richiesta di cancellazione è
quindi soddisfatta eliminando la mappatura. Mantieni `subject` puntato all'organizzazione tenant, non a una persona:
```python
from halo_record import build
build("tool_call", "privacy", subject={"id": "acme", "name": "Acme Corp"})
```
Nessuna impostazione impone questo — è una disciplina nel modo in cui chiami il registratore.
Rende la cancellazione gestibile; non è anonimizzazione, e non esiste ancora
una conservazione o potatura integrata.
[LIMITS.md sezione 13](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#13-personal-data-and-erasure) contiene l'elenco completo dei campi,
spiega perché l'impronta dell'input memorizzata può confermare un valore indovinabile anche
dopo che la mappatura è scomparsa, e termina con le domande che un revisore dovrebbe porre.
Registra una chiamata al modello (la prima domanda dell'acquirente: "quale modello ha visto i miei dati?"):
```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
```
Il modello è inoltre sigillato come `agent.model` e i termini di conservazione come `data.zdr`,
così la colonna del modello dell'export e le regole di policy possono leggerli, ed entrambi sopravvivono
a `summaries=False`. Passa `messages=` e l'input del record porta quanti ce n'erano — un conteggio, mai il loro contenuto.
## Dove si colloca in uno stack di conformità
halo-record è un livello di evidenza, non una certificazione. Produce l'artefatto che i framework di valutazione continuano a richiedere con parole diverse. Una nota di ambito che governa ogni punto elencato di seguito: queste sono affermazioni di integrità sul record; la completezza rispetto all'operatore richiede un testimone esterno che detenga i checkpoint ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)).
- **Questionari di sicurezza e revisioni SOC 2:** rispondi alle sezioni AI con un Runtime Report verificabile invece che con screenshot e prosa.
- **AIUC-1:** produce l'evidenza di logging a prova di manomissione (E015.4) e i record della catena di esecuzione con eventi di autorizzazione (E015.2 — con una lacuna dichiarata: le tracce di ragionamento non vengono catturate) che il controllo di Accountability E015 dello standard nomina. E015 stesso è obbligatorio; E015.2 ed E015.4 sono il suo livello supplementare: non richiesti per il superamento, quello che un fornitore adotta quando un cliente o un regolatore lo richiede. Una volta che la catena è ancorata a un testimone di cui la parte affidante ha motivo di fidarsi — un testimone gestito dall'operatore stesso non fornisce questo — quella è una catena continuamente testimoniata anziché una ricostruita al momento dell'audit (ciò che entra nella catena è comunque limitato dalla superficie di cattura). Una mappatura delle evidenze controllo per controllo, incluso ciò che è deliberatamente fuori ambito, è in [`AIUC.md`](https://github.com/bkuan001/halo-record/blob/main/AIUC.md).
- **OWASP Top 10 for Agentic Applications 2026:** otto delle dieci minacce si mappano su regole di policy deterministiche sul record, due sono contrassegnate come fuori ambito con motivazioni, e il pacchetto è incluso nel package, eseguibile per nome (`halo policy chain.jsonl owasp-starter`). Una mappatura approssimativa della community, non un artefatto ufficiale OWASP. Vedi [`OWASP.md`](https://github.com/bkuan001/halo-record/blob/main/OWASP.md).
- **AARM (CSA):** produce la ricevuta d'azione a prova di manomissione che AARM specifica — R5, e la metà di sigillatura di R6 (l'identità è sigillata nell'hash, non autenticata crittograficamente). halo-record è il livello della ricevuta; abbinalo a un gateway di enforcement per un sistema AARM completo. Vedi [`AARM.md`](https://github.com/bkuan001/halo-record/blob/main/AARM.md).
---
[Read more](https://github.com/bkuan001/halo-record)