Tenant logs, tree heads & witnesses
A signature proves who signed a record. It does not prove the signer kept every record, or did not quietly rewrite history and re-sign it. That is what the log and its witnesses are for — and it is the gap the programme was built to close: a log is only as good as the party that keeps it, and the operator who ran the action is not a disinterested witness.
One log per tenant
Section titled “One log per tenant”Each tenant has its own append-only RFC 6962 / RFC 9162 Merkle tree. One tenant’s records can therefore be proven to that tenant’s auditor without revealing a single field of another tenant’s.
log_id |
hex(SHA-256(BRUTOR_DEPLOYMENT_ID ‖ 0x00 ‖ tenant_id)) — never a name or a path |
| Leaf | SHA-256(0x00 ‖ statement bytes) — the leaf commits to the Signed Statement |
| Node | SHA-256(0x01 ‖ left ‖ right) |
| Entry kinds | record, tree_head, disclosure, witness_backfill |
| Concurrency | the flush transaction takes pg_advisory_xact_lock(hashtext('evidence_log:' ‖ tenant_id)), allocates the sequence, appends leaves and nodes — gap-free across replicas |
Nodes are append-only (evidence_log_nodes); a node exists only for a full range, and the
right-hand partial ranges are folded on demand exactly as RFC 6962’s recursion prescribes.
Tree heads
Section titled “Tree heads”A signed tree head is issued when either:
BRUTOR_EVIDENCE_HEAD_CADENCE_ENTRIES(default 100) entries have been appended since the last head, or- the first entry after the last head is older than
BRUTOR_EVIDENCE_HEAD_CADENCE_SECONDS(default 900, 15 minutes).
The cadence is evaluated in the same transaction as the appends that made a head due. An idle log is never re-signed — there is nothing new to attest, and re-signing the same root would only manufacture the appearance of activity.
A head is a Signed Statement (full-content, application/vnd.brutor.tree-head+cbor,
iss = log_id, sub = "<log_id>#<tree_size>") over:
{ log_id, tree_size, root, prev_size, prev_root, issued_at, consistency_proof }consistency_proof is the RFC 6962 proof from prev_size to tree_size, so each head
proves it extends the previous one. After signing, the head is appended to its own log
as a tree_head entry: head N is covered by head N+1’s root, so flipping a byte in a stored
head breaks the next root.
Just before a head is signed, each audit row-chain writer that carried this tenant’s rows
and advanced its checkpoint seals an fyi record ai.brutor.audit-chain-head
(context: {chain_id, seq_end}, references to the chain head hash and the checkpoint
signature). The head that follows covers it, so a relying party who trusts the witnessed
tenant log also holds a witnessed commitment to the audit row chain
up to seq_end — rewriting proxy_logs would mean rewriting the witnessed log too.
Force a head (e.g. before building a bundle — the bundle endpoint does this itself):
POST /v1/evidence/logs/{tenant_id}/tree-head → {"issued": true, "head": {…}}, or
{"issued": false, "head": null} when nothing is uncovered.
Witnesses
Section titled “Witnesses”After commit, each head is registered with every enabled witness for the tenant — platform witnesses plus the tenant’s own (for example an auditor’s instance) — independently. Nothing here runs on the request path: fail-open for traffic, fail-loud for evidence.
| Kind | Protocol | What a verified receipt shows |
|---|---|---|
scitt |
RFC 9943 registration of the head statement (POST /transparency/register-statement) → an RFC 9942 COSE Receipt, attached to the head at unprotected label 394 to form a Transparent Statement |
the head is included, at a tree size, in the witness’s own append-only log |
rfc3161 |
an RFC 3161 TimeStampReq over SHA-256 of the head statement → a TimeStampToken |
the head existed at the token’s genTime |
Receipts are verified offline, under a pinned key — a key learned from the witness itself is display only, never a trust anchor:
- scitt:
vdsread from the protected header only; the inclusion proofcbor([tree_size, leaf_index, path])fromunprotected[396]; the leaf recomputed from the head statement; the path length checked against the expected depth; the root folded and the receipt signature checked against it with the witness’s pinned Ed25519 key. Vendor-private labels are surfaced undervendor_extand never trusted. - rfc3161: checked by
brutor-tspagainst the pinned TSA certificate — either the PEM, or the SHA-256 of its DER (64 hex), in which case the certificate is taken from the token and trusted only because its hash matches.
Per receipt the verdict is witnessed (scitt), timestamped (rfc3161), unverified (no
pin — not evidence of forgery) or invalid.
Witness outages are retried durably from the log itself, oldest first, every 5 minutes;
late receipts append as witness_backfill entries. Registration is idempotent per (head,
witness). Witness URLs are subject to the outbound policy.
Grades
Section titled “Grades”Each head gets a grade from its verified scitt receipts only:
| Grade | Condition |
|---|---|
standalone |
no verified independent receipt |
self_witnessed |
verified receipts only from witnesses flagged same_operator |
witnessed |
one verified receipt from an independently operated witness |
plural |
verified receipts from two or more distinct independent operators |
Grades never go up. An unpinned witness cannot raise a grade; a same-operator witness
can only make a head self_witnessed; a bundle’s grade is the minimum of its records’.
A timestamp never raises the grade: an RFC 3161 token proves when a head existed, not
that anyone else holds it in an append-only structure. A timestamp-only head stays
standalone, with its timestamp shown alongside.
Managing witnesses
Section titled “Managing witnesses”Settings → Evidence in the Admin UI, or the API:
curl -X POST http://localhost:5050/v1/admin/evidence/witnesses \ -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \ -d '{ "scope": "tenant", "kind": "scitt", "url": "https://transparency.auditor.example", "operator_label": "Example Audit LLP", "pinned_key": "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c", "same_operator": false }'scope is tenant (yours) or platform (system admins; used for every tenant). kind
is scitt or rfc3161. pinned_key is the witness’s raw Ed25519 public key in hex for
scitt, or the SHA-256 hex of the TSA certificate DER (a PEM also works where it fits) for
rfc3161. PATCH enables, relabels or re-pins; DELETE removes. Any RFC 9943
transparency service works — capsule-anchor, CCF-based ledgers, DataTrails-class
services, or another organisation’s brutor-witness.
The kill switch
Section titled “The kill switch”BRUTOR_EVIDENCE_WITNESS=off on the Core Proxy stops all witness registration (no egress).
Records and heads are still sealed; heads stay standalone and are shown so.
Unwitnessed alerting
Section titled “Unwitnessed alerting”Every 15 minutes the Control Plane scheduler raises an evidence_unwitnessed inbox
item for a tenant that runs a high-risk AI System when its newest tree head, issued more
than an hour ago, is still standalone or self_witnessed. The log is self-attested until
a witness countersigns; for a high-risk system that is a finding, not a footnote. A head
that is only timestamped still raises it — deliberately.
Running brutor-witness
Section titled “Running brutor-witness”The trial bundle ships a local RFC 9943 transparency service as the witness container
(ghcr.io/brutor-ai/brutor-witness): capsule-anchor (Apache-2.0), built unmodified from a
pinned commit, keeping its own RFC 6962 log in a brutor_witness database on the bundle’s
Postgres.
-
docker-start.shmintsBRUTOR_WITNESS_SIGNING_KEY(the witness’s authority key) andBRUTOR_EVIDENCE_SIGNING_KEYonce and keeps them in.env. -
On first boot the Control Plane registers the witness as a platform witness flagged
same_operator(BRUTOR_LOCAL_WITNESS_URL=http://witness:8000) and pins its key from/anchor/authority-pubkey, or fromBRUTOR_LOCAL_WITNESS_PINNED_KEYwhen set. -
Heads start receiving receipts and grade
self_witnessed— ordering proven, independence not claimed. -
For
witnessed, add an independent service (an auditor’s instance, or a public transparency service) under Settings → Evidence.
Its read-only log endpoints (/anchor/sth, /anchor/consistency-proof,
/anchor/authority-pubkey) are published on 127.0.0.1:${WITNESS_PORT:-8300} for your own
monitoring. The bundle keeps its public-log and TSA rails off (no external calls).
Prove the whole path end to end on a running bundle:
./scripts/evidence-smoke.shIt seals a record, forces a head, waits for the witness receipt and checks the head grades
self_witnessed under the pinned key, builds a bundle, verifies it (core verifier, and
brutor-verify when it is on PATH), then flips one byte of the record statement and one
of the receipt and checks that both fail.

