L7 Audit and disclosure
L7 is the regulator-facing surface. Every signed bundle, every policy evaluation, every key mutation, every state change lands here. The log is append-only, indexed for the regulator, and anchored hourly on Base. The retention horizon is seven years (BSA + AMLD).
What it does
For every signed action in the system, L7:
- Writes one row to
oris_audit_logwith tenant scoping (RLS enforced). - Computes a hash chain link to the previous row.
- Encrypts the unredacted disclosure into a sealed envelope (AGE threshold).
- Auto-flags rows that match SAR rules.
- Hourly: computes a Merkle root over the new entries and anchors it on chain.
- Fires HMAC-SHA256 webhooks to subscribed regulator portals.
The row references stay in TimescaleDB. The unredacted contents stay in the sealed envelope. Oris cannot read the envelope without the regulator quorum.
Schema
oris_audit_log id uuid PK tenant_id uuid (RLS) bundle_id_keccak bytes32 bundle_id_sha256 bytes32 agent_did string action enum // payment.completed, payment.blocked, // policy.updated, key.rotated, kya.promoted, // kya.demoted, revocation.added, ... verdict enum // allow / deny / escalated reason_code string sar_flagged bool sealed_envelope_id uuid // pointer to encrypted disclosure hash_chain_prev bytes32 hash_chain_curr bytes32 // sha256(prev || row_canonical) created_at timestamptz retention_until timestamptz // 7 years from created_atTwelve fields, row-level security enforced by tenant_id. The hash chain is a SHA-256 link, scoped per tenant.
Hash chain integrity
Every entry includes hash_chain_prev and hash_chain_curr. Tampering with any row breaks the next row’s link. A verify endpoint walks the chain and returns:
{ "valid": true, "chain_length": 4821, "anchor_matches": true, "last_anchor_block": 42019099}anchor_matches confirms the chain head matches the hourly Merkle anchor on chain.
On-chain anchor
OrisAuditLogRegistry Base Sepolia0xAde6DC06178904194FaE72CC83C6d2ec65Ed34c8Every hour Oris computes a Merkle root over the new entries and writes it to the registry. The contract is pausable and append-only. Even a privileged operator with full database access cannot rewrite history. The on-chain anchor catches them.
Sealed envelope
The envelope is AGE-encrypted with the regulator quorum’s public keys (typically 3-of-5 threshold). Inside:
- the full unredacted bundle,
- KYC artifacts that back the agent identity,
- operator signing identity that approved the policy,
- escalation approvals fired during the transaction.
Sealing happens at L4 bundle assembly time. The envelope rides into L7 audit log as a pointer. Oris stores the ciphertext but cannot read it. See sealed envelope for the disclosure flow.
SAR auto-flag
Rows that match any of these conditions get sar_flagged = true:
risk_tier = Highorrisk_tier = Blockedsanctions_clean = falseamount_usd_e6 >= 10_000_000_000(ten thousand USD threshold)- Tier 1 revocation event for the agent within the last 24 hours
Flagged rows roll up to the regulator review queue accessible through the regulator portal.
Webhook subscriptions
Regulator portals can subscribe to flagged events via webhook:
POST /v1/audit/subscriptions{ "endpoint": "https://regulator.example.gov/oris-webhook", "events": ["sar.flagged", "revocation.tier1"], "secret": "shared_hmac_secret"}Every dispatch is HMAC-SHA256 signed with the shared secret + the request timestamp. Retries: 5 attempts with exponential backoff (1m, 5m, 15m, 60m, 240m). Webhook payload includes the bundle ids and the audit row id, never the unredacted contents.
Retention
Seven years from created_at. After the retention window, a daily sweeper deletes the row and its sealed envelope. The on-chain anchor remains (it is just a Merkle root, no PII).
SDK example
from oris import OrisClient
client = OrisClient(...)
# Verify the hash chain integritystatus = client.audit.verify(developer_id="dev_...")assert status.valid is Trueassert status.anchor_matches is True
# Query audit trailrows = client.audit.list( agent_id=agent.id, action="payment.completed", start_date="2026-05-01", limit=50,)
for row in rows: print(row.id, row.action, row.verdict, row.sar_flagged)import { OrisClient } from 'oris-sdk';
const client = new OrisClient({ ... });
const status = await client.audit.verify({ developerId: 'dev_...' });if (!status.valid || !status.anchorMatches) { throw new Error('audit chain compromised');}
const rows = await client.audit.list({ agentId: agent.id, action: 'payment.completed', startDate: '2026-05-01', limit: 50,});
for (const row of rows) { console.log(row.id, row.action, row.verdict, row.sarFlagged);}Deploy state
Contract live on Base Sepolia. The layer passed its security audit with zero critical and zero important findings open.
Where to go next
- L8 SDKs for the SDK abstractions over the audit API.
- Audit compliance guide for fetching trails and validating proofs.
- Regulator portal guide for the disclosure path.
- Sealed envelope for the encryption model.