Audit log and bundle export
The Sill dashboard’s audit log surfaces every governed interaction Sill has observed for your account — Discovery beacons today, signed mandates and policy outcomes under Transactional. Each row corresponds to one record in Sill’s append-only, ed25519-signed, Merkle-chained audit envelope. From any record you can export the signed audit bundle that anchors it: a single signed envelope covering every record in the same per-batch scope, downloadable as JSON or as a server-rendered HTML document with the canonical signed JSON embedded.
What the audit log shows
Section titled “What the audit log shows”The audit log surfaces records as Sill writes them to the envelope. Each row carries:
- The visiting agent’s identity (matched against Sill’s identity registry, e.g. Anthropic, OpenAI, Google), or
unknown_agent/human_likelywhen the request did not match a registered agent. - The decision token —
observedfor Discovery records,approved/rejected/escalated_approved/escalated_rejected/verification_rejected/rejected_post_verifyfor Transactional records. - The site the interaction was scoped to, the policy version that ran (Transactional), and the evaluation timestamp in ISO-8601 UTC.
- The per-record ed25519 envelope signature, the previous-record hash that chains the record into the per-(site, decision_class, day) Merkle batch, and a
signing_key_ididentifying the KMS key version that produced the signature.
The visible mix is summarized in the header; rows are colored by decision. Detail panes expand a record into its full canonical shape, including the rule trace for Transactional records and the discovery context for Discovery records.
Audit log — app.sill.so Mandates view, dark theme. The OBSERVED pill is Discovery; APPROVED / REJECTED / ESCALATED are Transactional.
Exporting a bundle
Section titled “Exporting a bundle”Every audit record can be exported as an audit bundle. The bundle is the signed cryptographic primitive: a single ed25519 envelope over the canonical body, covering every record in the same per-batch scope. The scope is one signed envelope per (site, decision_class, UTC date), so exporting from any record in a batch returns the same set of records.
The dashboard exposes the export from a record’s detail panel as Export JSON. The bundle is also retrievable directly from the audit-bundle endpoint described in the API reference; see GET /v1/audit/{record_id}/bundle.
Record detail panel — Export JSON triggers the signed-bundle download. The HTML variant is requested by appending ?format=html to the same endpoint.
Formats
Section titled “Formats”Two formats are available today:
| Format | Endpoint | Filename | What you get |
|---|---|---|---|
| Signed JSON | GET /v1/audit/{record_id}/bundle | audit-bundle-{site_id}-{utc_date}-batch.json | The signed SignedAuditBundle envelope. The wire body is the cryptographic primitive. |
| HTML with embedded JSON | GET /v1/audit/{record_id}/bundle?format=html | audit-bundle-{site_id}-{utc_date}-batch.html | A server-rendered HTML document. The canonical signed JSON is embedded verbatim in a <script type="application/sill+json" id="sill-bundle"> block; cryptographic standing is identical to the JSON branch. |
The package primitive toNdjson (one canonical record per line) lives inside @sill/audit for internal export composition, but only the JSON and HTML formats are exposed as merchant-downloadable bundle formats today. Additional formats are on the roadmap.
Pending-anchor bundles
Section titled “Pending-anchor bundles”A bundle can be exported before its Merkle batch has closed. When that happens, the bundle is interim: the batch_roots[0].merkle_root carries a designated PENDING_MERKLE_ROOT sentinel and the dashboard renders a Pending anchor badge. The per-record envelope signatures verify normally; only the batch anchor is provisional. Re-exporting the same record after the batch closes returns the finalized root.
What the signed bundle contains
Section titled “What the signed bundle contains”The bundle body — the bytes the signature is computed over — is the following shape (extra fields for envelope_signature and signing_key_id are added by the signer):
{ "bundle_id": "01K…", "site_id": "01EXAMPLE00000000000000000", "decision_class": "discovery", "exported_at": "2026-06-22T15:42:10.123Z", "record_count": 17, "records": [ { "record_id": "01K…", "site_id": "01EXAMPLE00000000000000000", "evaluated_at": "2026-06-22T15:38:02.001Z", "policy_version": "2026-05-30", "rules_evaluated": [], "decision": "observed", "envelope_signature": "…base64url…", "prev_record_hash": "…base64url…", "merkle_root": "…base64url-sentinel-or-batch-root…", "retention_class": "discovery_default" } ], "batch_roots": [ { "utc_date": "2026-06-22", "merkle_root": "…base64url…", "leaf_count": 17 } ], "envelope_signature": "…base64url…", "signing_key_id": "sill-audit-envelope-v1"}All hashes and signatures are base64url-encoded; timestamps are ISO-8601 UTC; IDs are ULIDs.
Bundle signature recipe
Section titled “Bundle signature recipe”The bundle envelope is signed by the audit-envelope signing key (sill-audit-envelope-v1), distinct from the agent-card / ARD edge signing key. The signing input is built with a versioned domain separator so a bundle signature cannot be replayed as a per-record signature:
signing_input = SHA-256( utf8("sill-audit-bundle-v1") || 0x00 || JCS-canonicalize(body))signature = Ed25519-Sign(signing_input, audit_envelope_private_key)bodyis the bundle object withenvelope_signatureandsigning_key_idremoved.- Canonicalization is RFC 8785 (JCS).
- The signature is Ed25519 (JWS-EdDSA / RFC 8037).
- The audit-envelope public key for verifying bundles is published at
https://sill.so/.well-known/sill-audit-envelope-v1.pub.
Per-record envelope_signature values are signed using the same key but without the bundle domain-separator prefix — they sign the canonical record bytes directly. A verifier walks prev_record_hash from record to record to reconstruct the Merkle batch and confirm each record’s position in the chain. The general signature recipe is detailed in Verify a signature.
flowchart LR E[Edge mandate engine] -->|signed record| Q[Origin queue] Q -->|per-record ed25519 sign| AR[(audit_record)] AR -->|UTC-day batch closer| AB[(audit_batch · Merkle root)] D[Dashboard / API client] -->|GET /v1/audit/:id/bundle| API[Bundle endpoint] API -->|RLS-scoped SELECT| AR API -->|load batch row| AB API -->|sign bundle envelope| KMS[(Signing key)] KMS -->|envelope_signature| API API -->|JSON or HTML+embedded JSON| D
Verification flow
Section titled “Verification flow”sequenceDiagram
autonumber
participant V as Verifier
participant S as sill.so/.well-known
participant F as Audit bundle file
V->>F: Open bundle (JSON, or extract embedded JSON from HTML script block)
V->>V: body = bundle without envelope_signature + signing_key_id
V->>V: signing_input = SHA-256("sill-audit-bundle-v1" || 0x00 || JCS(body))
V->>S: GET /.well-known/sill-audit-envelope-v1.pub
V->>V: Ed25519-Verify(envelope_signature, signing_input, pub)
V->>V: For each record: verify per-record signature + walk prev_record_hash
If any step fails — a single flipped byte in the body, a wrong public key, or a broken prev_record_hash chain — verification fails. Treat any record or bundle whose signature does not verify as untrusted.
Authorization and tenant scoping
Section titled “Authorization and tenant scoping”The bundle endpoint is tenant-scoped via Postgres row-level security. A record_id that does not belong to the authenticated account returns 404 Not Found, never 403, so the existence of a foreign record is not leakable. Bundle composition, signing, and download all happen server-side; the dashboard fetches the body as a Blob and triggers the download client-side.
Frequently asked
Section titled “Frequently asked”Does the HTML bundle have a different cryptographic standing than the JSON bundle?
No. The HTML variant embeds the canonical signed JSON verbatim in a <script type="application/sill+json" id="sill-bundle"> block. A verifier extracts the contents of that block and runs the same recipe as it would for the JSON download.
What does the Pending anchor badge mean?
The per-(site, decision_class, UTC date) Merkle batch had not been closed yet when the bundle was exported. The bundle is valid and the per-record signatures verify, but the batch root carries a sentinel (PENDING_MERKLE_ROOT) rather than a finalized root. Re-exporting after the batch closes returns the finalized anchor.
Which key signs the bundle?
The audit-envelope signing key (sill-audit-envelope-v1). This is a different key from the agent-card / ARD edge signing key. The bundle’s signing_key_id records the key version that produced the signature; a future key rotation is dispatched on this value.
Is there an NDJSON or signed-PDF export today?
Not as a merchant-downloadable bundle format. The dashboard ships signed JSON and the HTML variant with the canonical JSON embedded. An NDJSON line format exists inside the @sill/audit package as a primitive but is not exposed at the wire. Additional export formats are on the roadmap.
Can I export a single record? The bundle scope is per-batch (one signed envelope per (site, decision_class, UTC date)) — exporting from any record in a batch returns every record in that batch under one signature. This is intentional: the batch is the cryptographic anchor unit. The bundle’s HTML rendering highlights the record you opened so the position in the batch (“record M of N”) is unambiguous.
Does Sill provide an SDK to verify a bundle? No SDK is required. Verification uses only off-the-shelf primitives — an RFC 8785 JCS canonicalizer, an ed25519 verifier, and SHA-256. See Verify a signature for the worked recipe.
See also
Section titled “See also”- Audit envelope — the append-only, Merkle-chained record store the bundle is exported from.
- Verify a signature — end-to-end verification recipe with a minimal JavaScript sketch.
- Public JWKS — the JWKS endpoint that publishes Sill’s edge signing keys (note: bundle verification uses the audit-envelope key at
/.well-known/sill-audit-envelope-v1.pub, distinct from the edge JWKS). - API endpoints — the wire surface for
GET /v1/audit/{record_id}/bundle. - Sites and onboarding — how a site is registered before the audit log starts receiving records.
- What is Sill — the identity / intent / proof framing the audit envelope provides “proof” for.