Merkle Settlement
Periodic Merkle anchoring of action receipts. Every notarized receipt eventually gets sealed into exactly one settlement; the settlement's Merkle root is the cryptographic commitment that the batch existed at a specific moment in time.
What it is
A settlement is a sealed batch of action receipts with a single SHA-256 Merkle root over their payload hashes. The Merkle root is signed with Aira's gateway key and (best-effort) anchored to an RFC 3161 trusted timestamp authority.
After a settlement is sealed:
- Every settled receipt has its
settlement_uuidandmerkle_proofpopulated. - A regulator can prove "this single receipt was in this settlement" in O(log n) using the inline
merkle_proof— no need to hold the whole receipt list. - The settlement's Merkle root + signature is the cryptographic commitment that the batch existed at the moment it was sealed.
This is the F8 piece from the LangChain RFC #35691 thread: APS-style periodic anchoring without external blockchain dependence.
Settlement vs Compliance Bundle
Aira has two batch-receipt primitives. They look similar but serve different jobs:
| Compliance bundle | Settlement | |
|---|---|---|
| Trigger | User, ad-hoc | Admin / scheduled |
| Scope | User-chosen date range, framework-mapped | All unsettled receipts at the moment of seal |
| Membership | A receipt can be in many bundles | A receipt is in exactly one settlement |
| Output | Self-contained JSON for a regulator (downloadable) | Inline merkle_proof on each receipt row |
| Use case | "Q1 2026 evidence packet for the EU AI Act audit" | "Cryptographic commitment that yesterday's receipts existed by 06:00 UTC" |
Both reuse the same RFC 6962 Merkle primitive. Use compliance bundles when you need an export. Use settlements when you need automatic, append-only anchoring with per-receipt inclusion proofs.
Seal a settlement
Admin endpoint
curl -X POST https://api.airaproof.com/api/v1/settlements \
-H "Authorization: Bearer aira_live_xxx"Returns the new settlement, or null (still 201) if there were no unsettled receipts:
{
"id": "s-...",
"org_uuid": "...",
"period_start": "2026-04-10T00:00:00Z",
"period_end": "2026-04-10T23:59:59Z",
"receipt_count": 1247,
"merkle_root": "fc2a8b...",
"settlement_hash": "sha256:...",
"signature": "ed25519:...",
"signing_key_id": "aira-signing-key-v1",
"timestamp_token": "MIIB...",
"sealed_at": "2026-04-11T00:00:00Z",
"request_id": "..."
}Scheduled
In production, run the seal on a schedule via Celery beat:
# app/tasks/settlement_task.py
from celery.schedules import crontab
from app.celery_app import app as celery_app
from app.core.database import async_session
from app.services import settlement_service
from app.models.organization import Organization
@celery_app.task
async def settle_all_orgs():
async with async_session() as db:
orgs = (await db.execute(select(Organization))).scalars().all()
for org in orgs:
await settlement_service.create_settlement(db, org.id)
await db.commit()
celery_app.conf.beat_schedule = {
"hourly-settlement": {
"task": "app.tasks.settlement_task.settle_all_orgs",
"schedule": crontab(minute=0),
},
}Whatever cadence you pick (every hour, every N receipts, end-of-day), each settlement seals only the receipts that have not yet been sealed. Re-running on an empty set is a no-op that returns null.
Inclusion proofs
Every settled receipt carries its own Merkle inclusion proof on action_receipts.merkle_proof. The shape mirrors app.services.merkle.MerkleProof:
{
"leaf_hash": "abc123...",
"index": 42,
"leaf_count": 1247,
"siblings": ["...", "...", "..."]
}To fetch a receipt's proof in standalone form:
curl https://api.airaproof.com/api/v1/settlements/inclusion-proof/<receipt_uuid> \
-H "Authorization: Bearer aira_live_xxx"{
"settlement_uuid": "s-...",
"receipt_uuid": "r-...",
"merkle_root": "fc2a8b...",
"leaf_hash": "abc123...",
"index": 42,
"leaf_count": 1247,
"siblings": ["...", "...", "..."],
"request_id": "..."
}Verify an inclusion proof offline
import hashlib
LEAF_PREFIX = b"\x00"
NODE_PREFIX = b"\x01"
def hash_node(left: bytes, right: bytes) -> bytes:
return hashlib.sha256(NODE_PREFIX + left + right).digest()
def verify_inclusion(proof: dict, root_hex: str) -> bool:
"""Walk the siblings from leaf to root and confirm we land on the
published Merkle root. Pure function — no Aira dependency."""
cursor = bytes.fromhex(proof["leaf_hash"])
idx = proof["index"]
for sibling_hex in proof["siblings"]:
sibling = bytes.fromhex(sibling_hex)
if idx % 2 == 0:
cursor = hash_node(cursor, sibling)
else:
cursor = hash_node(sibling, cursor)
idx //= 2
return cursor.hex() == root_hex
# Example
import json
with open("inclusion-proof.json") as f:
proof = json.load(f)
assert verify_inclusion(proof, proof["merkle_root"]), "Proof does not verify"
print("Receipt", proof["receipt_uuid"], "is provably in settlement", proof["settlement_uuid"])The verifier is a pure function. A regulator can paste it into their own script and confirm the receipt was sealed into the settlement, without ever calling Aira.
Webhook
Aira fires receipts.settlement_sealed whenever a new settlement is created:
{
"event": "receipts.settlement_sealed",
"settlement_uuid": "s-...",
"org_uuid": "...",
"receipt_count": 1247,
"merkle_root": "fc2a8b...",
"sealed_at": "2026-04-11T00:00:00Z"
}Subscribe via Dashboard → Webhooks → Add endpoint → receipts.settlement_sealed and pipe it into your alerting / archive pipeline.
What this neutralizes
- APS's settlement pattern — Aira ships periodic Merkle anchoring out of the box, with per-receipt inclusion proofs and a public verification path.
- The implicit "all your receipts live in our database" risk. Even if Aira's row-level data is lost or tampered with, the published Merkle roots are append-only commitments to the receipts that existed at each settlement window.
Combined with the public verify-action endpoint and the JWKS-published signing key, every receipt has at least three independently verifiable trust statements:
- The Ed25519 signature verifies against the published gateway public key (per-receipt).
- The policy-evaluator signature verifies against the published evaluator public key (per-evaluation, multi-party).
- The Merkle inclusion proof verifies against the published settlement root (per-batch).
A regulator can confirm any one of these without trusting the other two.
Multi-Party Signatures
Aira's chain of custody has two independent signatures: the action gateway signs the receipt, the policy evaluator signs the upstream policy decision. A regulator can verify both against JWKS without trusting Aira to mediate.
Compliance & Regulation
How Aira maps to EU AI Act, SR 11-7, GDPR, and other regulatory frameworks.