Skip to content

Provenance + Audit

Every Hub record carries a SHA-256 provenance hash over its canonical JSON form. The control plane co-signs the hash so the data plane and control plane bind cryptographically without leaking content. The governance_event chain reconstructs “what did the AI do for whom on what date?” — for 7 years back.

Three audit questions Claresia must answer end-to-end:

  1. “Did this Hub record really originate from a Claresia skill invocation?” → Provenance hash + co-sign verification.
  2. “Has this Hub record been tampered with?” → Re-compute provenance hash on read; compare to stored hash + co-sign.
  3. “Who took the privileged action that changed this RBAC / config / skill?”governance_event chain, ordered by monotonic created_at.

The exact procedure (mirrored byte-for-byte in Python and TypeScript):

  1. Take the record dict
  2. Exclude provenance_hash, provenance_cosign
  3. Convert to canonical JSON:
    • Sort keys lexicographically (UTF-8 byte order)
    • No whitespace
    • Whole-valued floats normalized to ints (1.01)
    • null for missing optional fields
    • UTF-8 encoding
  4. SHA-256 the bytes
  5. Base64-encode the digest (URL-safe, no padding)
# Python reference (claresia_hub/canonical.py)
def compute_provenance_hash(record: dict) -> str:
payload = {k: _normalize(v) for k, v in record.items()
if k not in ("provenance_hash", "provenance_cosign")}
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(canonical.encode("utf-8")).digest()
return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
// TypeScript reference (claresia-hub/canonical.ts)
export function computeProvenanceHash(record: Record<string, unknown>): string {
const { provenance_hash: _h, provenance_cosign: _c, ...rest } = record;
const normalized = normalize(rest);
const canonical = canonicalStringify(normalized);
const digest = createHash("sha256").update(canonical, "utf8").digest();
return digest
.toString("base64")
.replace(/=+$/, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}

Cross-language fixtures (cross_language_fixtures.json) verify byte-equality in CI on every commit.

After the data plane writes a record + provenance hash, the control plane co-signs the hash with its per-tenant signing key (Ed25519). The co-sign binds the data plane to the control plane:

  • Data plane cannot forge a record (no control-plane signing key)
  • Control plane cannot forge a record (no data-plane content)
  • Tampering with either side breaks verification

The signing key is stored in HSM (AWS KMS / Azure Key Vault HSM tier).

When you read a record via the Hub API with ?verify=true:

  1. Hub server fetches the record
  2. Computes the provenance hash over the canonical JSON
  3. Compares to the stored provenance_hash
  4. Verifies the provenance_cosign against the control-plane signing key
  5. Returns the record + a verification_status field (verified, tampered, cosign_invalid)

If verification_status != "verified", a governance_event of kind hub.tamper_detected is automatically emitted.

Every privileged action emits a governance_event Hub record. Together they form the canonical audit log. Common kinds:

KindTriggered by
auth.login, auth.failedWorkOS callback
rbac.role_assigned, rbac.archetype_overriddenIT admin action in Command Center
connector.credential_rotatedIT admin rotates LLM API key
skill.entitlement_changedIT admin toggles a skill
cmek.key_rotated, cmek.key_revokedCustomer rotates CMEK
hub.record_purgedRetention policy execution
hub.tamper_detectedVerification failure on read
tenant.config_changedAny tenant settings change

Each event includes:

  • Actor (user_id, source IP, session_id)
  • Subject (resource_id, resource_type)
  • Outcome (success / failure + error code)
  • Payload (full diff of the change)
  • Timestamp (microsecond precision)
  • Co-signed provenance hash

Reconstructing “what happened” for an auditor

Section titled “Reconstructing “what happened” for an auditor”

Typical audit query (using the Hub API):

Terminal window
# All actions on tenant=dainese, last 30 days, by user=it.admin
curl -X GET 'https://api.claresia.com/hub/v1/records' \
-H 'Authorization: Bearer $JWT' \
-G \
--data-urlencode 'tenant_id=dainese' \
--data-urlencode 'record_type=governance_event' \
--data-urlencode 'created_by=user:it.admin@dainese.it' \
--data-urlencode 'created_at__gte=2026-04-01T00:00:00Z' \
--data-urlencode 'verify=true'

Returns a paginated list, every record verified, every record co-signed. Export as CSV or JSON-Lines for your auditor.

The governance_event table has a fixed 7-year retention (regulatory requirement for SOC 2, GDPR Article 30, NIS2). Records cannot be deleted before then via the Hub API. The only way to delete governance_event records before 7 years is a legal hold release signed off by Claresia’s CTO + the customer’s compliance officer.

Other record types follow per-table retention defaults (overridable per tenant). See Retention.

  • Right to erasure (GDPR Art. 17) is honored: end-user records can be purged on request, except for governance_event records which fall under the legal-obligation lawful basis (Art. 17(3)(b)).
  • Data Processing Agreement ships with Schrems II Standard Contractual Clauses + technical supplementary measures. See DPA template.
  • Sub-processors disclosed at trust.claresia.com.

In Mode C, customers can verify the Hub themselves:

Terminal window
# Install the verifier (open source)
pip install claresia-hub-verifier
# Verify all records in a tenant
claresia-hub-verifier verify \
--backend postgres \
--dsn 'postgres://...' \
--tenant dainese \
--cosign-public-key ~/.claresia/cosign-pub.pem
# Sample output:
# 1,234,567 records verified
# 0 tampered
# 0 cosign invalid
# Verification took 3m 41s

Run this as part of your regular compliance evidence-gathering — the output is audit-grade and machine-readable (JSON-Lines).