User Guide
September 14, 2026 · View on GitHub
Integrate and use Trust Layer in 5 minutes
What is it?
ArkForge Trust Layer adds a verifiable cryptographic proof to any API call.
Before:
response = requests.post("https://provider.com/api", json={...})
# → You have the result, but no proof
After:
response = requests.post("https://trust.arkforge.tech/v1/proxy",
headers={"X-Api-Key": "mcp_xxx..."},
json={"target": "https://provider.com/api", "payload": {...}})
# → You have the result + a verifiable cryptographic proof
Effort: ~5 lines of code changed.
Autonomous agents — how budget works
Trust Layer is designed for agents that run without human intervention. Here is the complete flow:
Step 1 — One-time manual setup (human)
The first time, a human opens a browser, enters a card, and completes the Stripe Checkout. The card is saved in Stripe for future charges.
curl -X POST https://trust.arkforge.tech/v1/keys/setup \
-d '{"email": "agent@example.com", "amount": 10}'
# → {"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_..."}
# Open the URL, enter card → key delivered by email
This is the only human action required.
Step 2 — Agent runs autonomously forever
From that point on, the agent checks its balance before each task and recharges automatically if needed — no browser, no human, no interruption.
import requests
class AutonomousAgent:
def __init__(self, api_key, min_balance=5.0, recharge_amount=10.0):
self.api_key = api_key
self.min_balance = min_balance
self.recharge_amount = recharge_amount
self.headers = {"X-Api-Key": api_key, "Content-Type": "application/json"}
def ensure_budget(self):
"""Recharge automatically if balance is low."""
usage = requests.get(
"https://trust.arkforge.tech/v1/usage",
headers=self.headers
).json()
balance = usage['credit_balance']
if balance < self.min_balance:
result = requests.post(
"https://trust.arkforge.tech/v1/credits/buy",
headers=self.headers,
json={"amount": self.recharge_amount}
).json()
# result contains: credits_added, balance, charge_id, receipt_url
print(f"Recharged: +{result['credits_added']} EUR, new balance: {result['balance']} EUR")
def execute(self, target, payload):
self.ensure_budget()
return requests.post(
"https://trust.arkforge.tech/v1/proxy",
headers=self.headers,
json={"target": target, "payload": payload}
).json()
# The agent manages its own budget indefinitely
agent = AutonomousAgent("mcp_pro_xxx...", min_balance=5.0, recharge_amount=10.0)
while True:
result = agent.execute("https://provider.com/api", {"task": "analyze", "data": "..."})
proof_id = result['proof']['id']
# → Agent runs, pays for proofs, recharges itself — zero human input
What happens under the hood:
POST /v1/credits/buytriggers a Stripe off-session charge on the card saved during setup- The saved card is charged immediately — no redirect, no browser
- Credits are added to the account instantly
- The
receipt_urlin the response is a verifiable Stripe receipt for the recharge
Summary:
| Step | Who | Action |
|---|---|---|
| Initial setup | Human | Open Stripe Checkout, subscribe (Pro €29/month or Enterprise €149/month) |
| Execute tasks | Agent | POST /v1/proxy — included in monthly quota |
| Overage credits (opt-in) | Agent | POST /v1/credits/buy — only if overage billing is enabled |
MCP integration (Model Context Protocol)
MCP is the standard protocol for connecting AI agents to tools. ArkForge certifies each outbound tool call from your MCP server — turning every tools/call into a verifiable Agent Action Receipt (AAR).
How it works
Your MCP server normally calls external APIs directly. You redirect those calls through POST /v1/proxy. The rest of your code is unchanged.
Claude / any MCP client
│ tools/call
▼
Your MCP Server
│ HTTP call (redirected)
▼
POST /v1/proxy → ArkForge → External API
↓
Signed proof: tool name + args + response
RFC 3161 timestamp + Sigstore Rekor anchor
Integration pattern
import httpx
TRUST_LAYER_URL = "https://trust.arkforge.tech/v1/proxy"
ARKFORGE_API_KEY = "mcp_pro_xxx..."
def certified_call(target_url: str, payload: dict, tool_name: str) -> dict:
"""Wrap any outbound MCP tool call with a Trust Layer proof."""
resp = httpx.post(
TRUST_LAYER_URL,
headers={"X-Api-Key": ARKFORGE_API_KEY, "X-Agent-Identity": tool_name},
json={
"target": target_url,
"method": "POST",
"payload": payload,
"description": f"MCP tool call: {tool_name}",
},
timeout=30,
)
result = resp.json()
proof_id = result["proof"]["id"]
# → proof publicly verifiable at https://trust.arkforge.tech/v1/proof/{proof_id}
return result["response"]
Replace direct httpx.post(target_url, ...) calls in your MCP server with certified_call(target_url, ...). Every tool call is now an AAR.
What each AAR contains
| Field | Content |
|---|---|
tool_name | Value of X-Agent-Identity header |
request_hash | SHA-256 of the exact payload sent |
response_hash | SHA-256 of the exact response received |
timestamp | RFC 3161 certified (independent TSA) |
signature | Ed25519, verifiable with ArkForge public key |
rekor_log_id | Entry in Sigstore public append-only log |
Multi-tool MCP server example
# In your MCP server — before
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
if name == "search_web":
return await httpx.post("https://search-api.example.com/search", json=arguments)
if name == "send_email":
return await httpx.post("https://mail-api.example.com/send", json=arguments)
# After — one line change per tool
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
if name == "search_web":
return certified_call("https://search-api.example.com/search", arguments, "search_web")
if name == "send_email":
return certified_call("https://mail-api.example.com/send", arguments, "send_email")
Every tool call now has an independent proof your client can verify — without trusting your server logs.
Two modes
Mode A — Transaction proof only
Use this if you want to prove that a transaction took place (no payment proof needed).
What is proven:
- Request sent (SHA-256 hash)
- Response received (SHA-256 hash)
- Certified timestamp (RFC 3161)
- Trust Layer signature (Ed25519)
- No payment proof
Example: Autonomous agent consuming a service — you just want a verifiable trace.
Mode B — Transaction + payment proof
Use this if you want to prove both that a transaction took place and that a payment was made.
What is proven:
- Request sent (SHA-256 hash)
- Response received (SHA-256 hash)
- Certified timestamp (RFC 3161)
- Trust Layer signature (Ed25519)
- Payment to the provider (Stripe receipt hash)
Example: Financial audit, regulatory compliance, required payment proof.
Quick start
Step 1 — Get an API key
Option A — Free key (500 proofs/month)
curl -X POST https://trust.arkforge.tech/v1/keys/free-signup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
Response:
{
"api_key": "mcp_free_abc123...",
"plan": "free",
"limit": "500 proofs/month"
}
No credit card required.
Option B — Pro key (€29/month, 5,000 proofs)
B.1 — Test mode (for development)
curl -X POST https://trust.arkforge.tech/v1/keys/setup \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"plan": "pro",
"mode": "test"
}'
Response:
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...",
"plan": "pro",
"mode": "test"
}
Instructions:
- Open
checkout_urlin a browser - Test card:
4242 4242 4242 4242 - Expiry: any future date (e.g. 12/30)
- CVC: any 3 digits (e.g. 123)
- Confirm → receive
mcp_test_xxx...by email
B.2 — Pro production (€29/month, 5,000 proofs/month)
curl -X POST https://trust.arkforge.tech/v1/keys/setup \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"plan": "pro"
}'
Response:
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...",
"plan": "pro",
"mode": "live"
}
Instructions:
- Open
checkout_urlin a browser - Enter your real card details
- Confirm → €29/month subscription started
- Receive
mcp_pro_xxx...by email
Note: This is the only manual step. Everything after is automatic.
Overages (opt-in): by default, requests beyond 5,000 proofs/month are rejected (HTTP 429). You can opt in to overage billing at 0.01 EUR/proof from prepaid credits — enable at POST /v1/keys/overage.
B.3 — Manage your subscription
Open the Stripe Billing Portal to update your payment method, download invoices, or cancel:
curl -X POST https://trust.arkforge.tech/v1/keys/portal \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{}'
# Returns: {"portal_url": "https://billing.stripe.com/..."}
Open portal_url in a browser.
Subscription lifecycle:
| Event | What happens |
|---|---|
| Subscription started | API key created and emailed |
| Monthly renewal | Key stays active, no action needed |
| Payment failed (Stripe retry) | Key stays active during retry period |
| Subscription suspended (payment exhausted) | Key deactivated (HTTP 401) |
| Payment resolved → invoice paid | Key automatically reactivated — no action required |
| Subscription cancelled | Key deactivated permanently |
If your key is suspended due to a failed payment, update your card in the billing portal. Once Stripe collects the payment, your key is reactivated automatically within seconds.
Step 2 — Call the Trust Layer
Mode A — Transaction proof
import requests
TRUST_LAYER_API_KEY = "mcp_free_xxx..." # or mcp_test_xxx or mcp_pro_xxx
TARGET_API = "https://provider.com/api/service"
response = requests.post(
"https://trust.arkforge.tech/v1/proxy",
headers={
"X-Api-Key": TRUST_LAYER_API_KEY,
"Content-Type": "application/json"
},
json={
"target": TARGET_API,
"payload": {
"task": "analyze",
"data": "hello world"
},
"description": "Compliance analysis"
}
)
result = response.json()
# Upstream service result
service_response = result['service_response']['body']
print(f"Result: {service_response}")
# Cryptographic proof
proof = result['proof']
print(f"Proof ID: {proof['proof_id']}")
print(f"Verify at: {proof['verification_url']}")
What happens:
- Trust Layer forwards the request to the provider
- Trust Layer hashes request + response (SHA-256)
- Trust Layer generates a proof with Ed25519 signature
- You receive: result + proof
Proof contains:
- Unique ID (e.g.
prf_20260302_135727_5b47d5) - SHA-256 hash of the request
- SHA-256 hash of the response
- Certified timestamp (RFC 3161)
- Ed25519 signature
- Public verification URL
No payment proof (Mode A).
Mode C — Certifying an action on a third-party API (extra_headers)
Use this when the target API requires its own authentication (GitHub token, Slack token, etc.). Pass the credentials in extra_headers — they are forwarded to the target and included in the proof's request hash.
import requests
TRUST_LAYER_API_KEY = "mcp_free_xxx..."
GITHUB_TOKEN = "ghp_xxx..."
response = requests.post(
"https://trust.arkforge.tech/v1/proxy",
headers={
"X-Api-Key": TRUST_LAYER_API_KEY,
"Content-Type": "application/json"
},
json={
"target": "https://api.github.com/repos/owner/repo/issues/5/comments",
"method": "POST",
"payload": {"body": "Automated analysis complete — see proof below."},
"description": "GitHub comment by autonomous agent",
"extra_headers": {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json"
}
}
)
result = response.json()
proof = result['proof']
print(f"Comment posted. Proof: {proof['verification_url']}")
What is certified:
- The exact payload sent to GitHub (SHA-256 hash of request)
- GitHub's response confirming the comment was created (SHA-256 hash of response)
- Timestamp (RFC 3161) + Ed25519 signature
Constraints on extra_headers:
- Maximum 10 headers
- Keys and values must be strings, values ≤ 4096 characters
- Blocked headers (silently dropped):
Host,Transfer-Encoding,Connection,Upgrade,Content-Length,Content-Type,X-Internal-Secret
Security note: extra_headers values are forwarded in transit through ArkForge infrastructure and are visible in memory during request processing. They are never logged or stored — only header names are recorded in the proof hash (values are replaced with ***). This means the proof attests that a given header was present, without revealing its value.
Mode B — Transaction + payment proof
import requests
import stripe
# 1. Agent pays provider DIRECTLY via Stripe
stripe.api_key = "sk_test_..." # Your Stripe key
# Note: simplified example. In practice, use a saved payment method
# (payment_method_id) and expand=["charges"] to retrieve the receipt URL.
payment = stripe.PaymentIntent.create(
amount=500, # 5.00 EUR in cents
currency="eur",
description="Analysis service",
payment_method="pm_card_visa", # saved payment method
confirm=True,
expand=["charges"]
)
receipt_url = payment.charges.data[0].receipt_url
print(f"Provider paid: {receipt_url}")
# 2. Agent requests certification from Trust Layer
# Free key is sufficient for Mode B (external payment, no credit deduction)
TRUST_LAYER_API_KEY = "mcp_free_xxx..."
TARGET_API = "https://provider.com/api/service"
response = requests.post(
"https://trust.arkforge.tech/v1/proxy",
headers={
"X-Api-Key": TRUST_LAYER_API_KEY,
"Content-Type": "application/json"
},
json={
"target": TARGET_API,
"payload": {
"task": "analyze",
"data": "hello world"
},
"description": "Analysis with payment proof",
"provider_payment": {
"type": "stripe",
"receipt_url": receipt_url
}
}
)
result = response.json()
proof = result['proof']
print(f"Proof ID: {proof['proof_id']}")
print(f"Spec version: {proof['spec_version']}") # → 3.0
provider_payment = proof['provider_payment']
print(f"Receipt hash: {provider_payment['receipt_content_hash']}")
print(f"Amount: {provider_payment['parsed_fields']['amount']} EUR")
print(f"Verify at: {proof['verification_url']}")
What happens:
- Agent pays provider via Stripe (5.00 EUR)
- Agent receives a receipt URL from Stripe
- Agent calls Trust Layer with
provider_payment - Trust Layer independently fetches the receipt from Stripe
- Trust Layer hashes the receipt (SHA-256)
- Trust Layer includes the receipt hash in the chain
- You receive: result + proof (spec 2.0)
Proof contains (Mode B):
- Everything from Mode A
- Receipt URL (
https://pay.stripe.com/receipts/...) - Receipt hash (SHA-256 of raw content)
- Parsed fields (amount, currency, status, date)
- Spec version 2.0
SSRF protection: Trust Layer only fetches from whitelisted domains (pay.stripe.com, receipt.stripe.com).
Step 3 — Trust Layer marks on every call
Beyond the proof object, Trust Layer watermarks every transaction at 3 levels:
Level 1 — Digital Stamp (JSON body)
An _arkforge_attestation field is injected into service_response.body:
{
"_arkforge_attestation": {
"id": "prf_20260302_135727_5b47d5",
"seal": "https://trust.arkforge.tech/v1/proof/prf_20260302_135727_5b47d5",
"status": "VERIFIED_TRANSACTION"
}
}
Level 2 — Ghost Stamp (HTTP headers)
Every proxy response includes X-ArkForge-Proof, X-ArkForge-Verified, X-ArkForge-Proof-ID, and X-ArkForge-Trust-Link headers — visible to gateways and middleware without body parsing.
Level 3 — Visual Stamp (HTML proof page)
Open https://arkforge.tech/trust/v/{proof_id} in a browser for a human-readable verification page with a colored badge (green = verified).
DID binding — cryptographic agent identity
By default, agent_identity in every proof receipt is caller-declared: whatever string you pass in X-Agent-Identity is recorded verbatim. Trust Layer does not verify it.
DID binding upgrades this to a cryptographically proven identity: the agent proves key ownership once at registration, and Trust Layer flows the verified DID into all subsequent receipts automatically.
Without binding: parties.agent_identity = "my-agent" (declared, unverified)
With binding: parties.agent_identity = "did:web:example.com" (resolved + verified)
parties.agent_identity_verified = true
Opt-in. Existing callers that pass a plain string or nothing are unaffected.
Path A — Challenge-response (did:web, did:key)
Use this when your agent holds its own Ed25519 private key.
Step 1 — Initiate binding
curl -X POST https://trust.arkforge.tech/v1/keys/bind-did \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{"did": "did:web:example.com"}'
Response:
{
"challenge": "arkforge-did-bind-a3f9...",
"expires_in": 300
}
Trust Layer resolves your DID document, extracts the Ed25519 public key, and issues a challenge string that expires in 5 minutes.
Step 2 — Sign the challenge
Sign the raw challenge bytes with your Ed25519 private key (no hashing — sign the UTF-8 string directly):
import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key: Ed25519PrivateKey = ... # your key
challenge = "arkforge-did-bind-a3f9..."
sig_bytes = private_key.sign(challenge.encode())
signature = base64.urlsafe_b64encode(sig_bytes).rstrip(b"=").decode()
Step 3 — Confirm binding
curl -X POST https://trust.arkforge.tech/v1/keys/bind-did/confirm \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d "{\"challenge\": \"arkforge-did-bind-a3f9...\", \"signature\": \"${signature}\"}"
Response:
{
"verified_did": "did:web:example.com",
"bound_at": "2026-03-30T13:00:00+00:00",
"method": "challenge_response"
}
From this point, all proxy calls from this API key include agent_identity_verified: true in the proof receipt — without any change to your call code.
Read the binding back
curl https://trust.arkforge.tech/v1/keys/identity -H "X-Api-Key: mcp_pro_xxx..."
Response (identity only: no email, plan or payment reference):
{
"verified_did": "did:web:example.com",
"verified_did_method": "challenge_response",
"verified_did_bound_at": "2026-03-30T13:00:00+00:00",
"verified_did_history": [
{"did": "did:key:z6Mk...", "bound_at": "2026-03-01T09:00:00+00:00", "method": "challenge_response", "unbound_at": "2026-03-30T13:00:00+00:00"}
]
}
Every change of DID or binding method appends the previous binding to verified_did_history.
Path B — OATR delegation (skip challenge)
If your agent is registered as a Tier 1 issuer in the Open Agent Trust Registry, you can bind in one call — no challenge-response needed. Tier 1 registration already proved key control to a stricter standard.
curl -X POST https://trust.arkforge.tech/v1/keys/bind-did \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{
"did": "did:web:example.com",
"oatr_issuer_id": "your-issuer-id"
}'
Response:
{
"verified_did": "did:web:example.com",
"bound_at": "2026-03-30T13:00:00+00:00",
"method": "oatr_delegation"
}
Trust Layer fetches the OATR registry manifest, verifies that oatr_issuer_id is active (Tier 1) and that the declared Ed25519 key matches the DID-resolved key. No second round-trip required.
Ed25519 key format compatibility
Trust Layer normalizes all incoming public keys to raw 32-byte representation at the verification boundary. The following formats are accepted per DID method:
| DID method | Key field in DID Document | Accepted format | Notes |
|---|---|---|---|
did:web | publicKeyMultibase | base58btc, prefix z | With or without multicodec prefix 0xed01 — both accepted |
did:web | publicKeyJwk | kty: OKP, crv: Ed25519, x: <base64url> | Standard JWK, padding optional |
did:key | Encoded in the DID itself | base58btc + multicodec 0xed01 (required by spec) | Decoded from did:key:z<multibase> directly |
did:oatr | public_key in registry manifest | raw base64url, no padding | Direct byte comparison after decode |
Verification method types recognized: Ed25519VerificationKey2020, Ed25519VerificationKey2018.
All formats resolve to the same 32-byte Ed25519 public key internally. If your DID document uses a different representation than listed above, binding will return did_resolution_failed.
Effect on proof receipts
Once bound, every proof receipt from this API key includes:
{
"parties": {
"agent_identity": "did:web:example.com",
"agent_identity_verified": true
}
}
Without binding:
{
"parties": {
"agent_identity": "my-agent",
"agent_identity_verified": null
}
}
The agent_identity_verified field can be used by verifiers to distinguish cryptographically proven identity from caller-declared strings.
Proxy limits
| Limit | Value | Error when exceeded |
|---|---|---|
| Target protocol | HTTPS only | invalid_target 400 |
| Methods | GET or POST | invalid_request 400 |
| Payload format | JSON only | invalid_request 400 |
| Response timeout | 120 seconds | proxy_timeout 504 — proof still issued |
| Response stored / hashed | 1 MB max | Truncated at 1 MB — proof covers truncated content |
| Daily cap | None for Free/Pro/Enterprise — full quota usable at any time. Test keys: 100/day | rate_limited 429 (test keys only) |
| Monthly quota | 500 / 5 000 / 50 000 (Free / Pro / Enterprise) | rate_limited 429 (unless overage enabled) |
extra_headers count | Max 10 | invalid_request 400 |
extra_headers value length | Max 4 096 chars | invalid_request 400 |
Unsupported by the proxy:
| Feature | Status |
|---|---|
Binary payloads (multipart/form-data, raw bytes) | Not supported — JSON only |
| Streaming / Server-Sent Events | Not supported — full response collected before proof |
WebSocket (Upgrade header) | Not supported — blocked |
| HTTP (non-TLS) targets | Not supported — HTTPS required |
| Private IPs / localhost | Blocked — SSRF protection |
Note on response truncation: if the target API returns more than 1 MB, the stored response is truncated. The proof's response_hash covers the truncated version. The full upstream response is not retrievable from Trust Layer — only the first 1 MB is stored.
Note on the 120s timeout: long-running target APIs (ML inference, batch jobs) may hit the timeout. In that case, Trust Layer returns proxy_timeout (504) but still issues a proof capturing the attempt. If this is a recurring issue, consider wrapping your target API in an async job pattern and calling Trust Layer only when the result is ready.
Note on payload encryption: Trust Layer certifies what it receives — it does not decrypt content. Standard REST APIs (GitHub, Stripe, OpenAI, etc.) send plaintext JSON over HTTPS: Trust Layer terminates TLS, sees the plaintext, and hashes the semantic content. If your payload is encrypted at the application layer before reaching Trust Layer, the proof certifies the ciphertext — not the plaintext content. The proof remains cryptographically valid, but cannot attest to what the payload says without the decryption key. This distinction matters if you need to prove specific field values (e.g. qty=1) in a dispute.
Verify a proof
Public URL:
https://arkforge.tech/trust/v/prf_20260303_161853_4d0904
Or via API:
curl https://trust.arkforge.tech/v1/proof/prf_20260303_161853_4d0904
Independent verification
Run the verifier. It needs Python 3.8+ and openssl, nothing else — no pip install,
no rekor-cli, no cosign:
curl -sO https://raw.githubusercontent.com/ark-forge/trust-layer/main/scripts/verify_proof.py
python3 verify_proof.py prf_20260303_161853_4d0904
Proof prf_20260913_140000_aa0000 — spec 3.0
[ OK ] chain hash: Merkle root of 6 field commitments, recomputed from public data
alone (proves nothing on its own)
[ OK ] Ed25519 (ArkForge): valid — but the signer is the issuer, not a third party
[ OK ] batch anchor: leaf 0 of 4 in batch batch_20260913_132956_516 — the anchored
root covers this chain hash
[ OK ] RFC 3161 timestamp: freetsa.org signed this batch root [independent]
[ OK ] Sigstore Rekor: batch root in the public log at index 2818228683, inclusion
proof and checkpoint valid; submitted by ArkForge's published key [independent]
VERDICT: VERIFIED — 2 independent witness(es) confirm this
batch root covering this chain hash existed and was attested outside
ArkForge's control.
It exits non-zero if any check fails. Read the script before running it — it is deliberately short, and a verification tool you have not read is just another party to trust.
What each witness is worth
| Witness | Can ArkForge produce it alone? | |
|---|---|---|
| 1 | Chain hash recomputation | Yes — whoever fabricates a proof produces coherent hashes. Self-consistency only. |
| 2 | Ed25519 signature | Yes — the signer is ArkForge. It proves issuance, not truth. |
| 3 | Batch inclusion proof | Yes — self-consistency again, but it is what carries witnesses 4 and 5 down to this individual proof. |
| 4 | RFC 3161 timestamp | No — an independent Timestamp Authority signed this hash at a point in time. |
| 5 | Sigstore Rekor entry | No — the hash is in a public append-only log operated by the OpenSSF. |
Only 4 and 5 are evidence against the issuer. A proof carrying neither is not a receipt, whatever the other three say.
The chain hash is a commitment root (spec 3.0)
hashes.chain is the Merkle root of one commitment per chain field:
commitment = sha256(field_name || 0x00 || nonce || canonical_json(value))
hashes.chain = RFC 6962 Merkle root of those commitments, fields in sorted order
Each field gets its own fresh 32-byte nonce, drawn per proof. So the public proof can publish every commitment, and you recompute the anchored chain hash from public data alone:
curl -s https://trust.arkforge.tech/v1/proof/prf_xxx > proof.json
jq -r '.commitments' proof.json # what is published
jq -r '.hashes.chain' proof.json # their Merkle root
Before spec 3.0 the chain hash was computed over the values themselves, and the public proof redacts the transaction id and the buyer fingerprint — so a third party could not recompute it at all. That gap is closed, and closing it published nothing new.
What the public proof hides, and what it does not
Of the chain fields, the public proof hides exactly two: transaction_id and
buyer_fingerprint. The others are served in clear next to their commitment:
| Chain field | In the public proof |
|---|---|
transaction_id, buyer_fingerprint | Hidden. Only the commitment is published. |
request_hash, response_hash | In clear, as hashes.request and hashes.response. |
timestamp, seller, upstream_timestamp, receipt_content_hash | In clear. |
agent_identity, agent_identity_verified, did_resolution_status, identity_consistent | In clear and opened: their nonces are published under disclosed (spec 3.1). |
Two things follow, and both matter if the calls you certify are confidential.
A hash is not a secret when its input can be guessed. hashes.request is the SHA-256 of
the canonical JSON of {target, method, payload, amount, currency} (plus the names of any
extra headers), and hashes.response
of the response body. For a predictable call, such as a GET on a known URL, a fixed payload
or a yes/no answer, anyone holding the public proof can confirm what was sent or received by
hashing candidates. Two proofs of the same call also carry the same hashes.request, which
links them even though the buyer fingerprint is hidden.
A clear value is not an anchored value. For every field outside the identity block, the
public proof shows the value but not the nonce. A third party recomputes the anchored root
from the commitments, but cannot check that the hashes.request displayed is the one that
was committed: the issuer could restate it and every witness would still pass. You, the
owner, can close that gap for any counterparty by disclosing the field (below).
Proofs issued before spec 3.0 keep their own algorithm: raw concatenation up to spec 1.1,
SHA-256 of canonical JSON for 1.2 and 2.1. spec_version says which applies and
verify_proof.py handles all three.
Selective disclosure
You hold the nonces. GET /v1/proof/{id}/full (your API key, your proof) returns them
alongside the committed values:
{
"commitment_nonces": {"seller": "d5908a0c…", "transaction_id": "7b31…"},
"chain_data": {"seller": "api.example.com", "transaction_id": "pi_3Pxxxx"}
}
To prove one field to a counterparty without revealing the others, hand them that field's
(nonce, value) pair — by email, in a dispute filing, in a contract annex, however you like.
There is no disclosure endpoint and no signed bundle: the commitment is already anchored, so
the pair alone is the proof.
{"disclosed": {"transaction_id": {"nonce": "7b31…", "value": "pi_3Pxxxx"}}}
python3 verify_proof.py prf_xxx --disclose disclosure.json
[ OK ] selective disclosure: 1 disclosed field(s) match their anchored commitment: transaction_id
A forged value is refused, and so is a nonce moved to another field — the field name is part of the preimage:
[ FAIL ] selective disclosure: transaction_id: value does not match its commitment
Every field you do not disclose stays behind its own independent nonce: opening one tells a
third party nothing about the others. That protects only what the public proof does not
already show, which today is transaction_id and buyer_fingerprint (see the table above).
Disclosing a field that is already in clear is still useful. Handing over the
request_hash or response_hash pair is what ties the value displayed in hashes.request
or hashes.response to the anchored commitment, which the public proof alone does not do.
Anchoring is per batch, not per proof
Chain hashes accumulate in a batch. The batch closes after 100 proofs or 10 minutes, whichever comes first, and one RFC 3161 request plus one Sigstore Rekor entry cover its Merkle root. Rekor is a public good operated by the OpenSSF; one permanent entry per transaction is not ours to write into it.
Each proof then carries its own inclusion proof down from that root:
"batch_anchor": {
"status": "anchored",
"batch_id": "batch_20260913_132956_516",
"leaf_index": 0,
"tree_size": 4,
"audit_path": ["…", "…"],
"root": "sha256:18748b6c…"
}
The anchor artefacts — the RFC 3161 token, the Rekor entry reference, the audit path — are embedded in every proof. You need nothing but the proof itself to verify it; there is no batch endpoint to fetch and trust.
Between issuance and batch close, a proof has no external anchor. That window is at most
ten minutes and the proof says so: batch_anchor.status is pending, and both
GET /v1/proof/{id}/verify and verify_proof.py report it as waiting, never as tampered.
[ SKIP ] batch anchor: batch batch_xxx has not closed yet — this proof carries no
external anchor at this point
VERDICT: NOT INDEPENDENTLY VERIFIED — nothing here that ArkForge could not have
produced on its own. Self-consistency is not a receipt.
That verdict is correct, not a bug: a proof still waiting for its batch genuinely has no third-party witness yet. Wait for the batch, then verify.
Doing it by hand
The two independent witnesses, step by step. Each has one detail you cannot guess from
the proof alone, which is why they are written out here. Both apply to the batch root
when batch_anchor.status is anchored, and to the chain hash itself for proofs issued
before batching.
RFC 3161. The timestamped artefact is the anchored hash decoded to its 32 raw bytes,
not the hex string. The certificates depend on the issuer: timestamp_authority.provider
records which TSA actually signed, since the pool fails over across FreeTSA, DigiCert and
Sectigo. FreeTSA's root is self-signed and must be fetched from FreeTSA; DigiCert and
Sectigo are in your system trust store and their tokens carry their own chain.
curl -s https://trust.arkforge.tech/v1/proof/prf_xxx > proof.json
jq -r '.timestamp_authority.provider' proof.json # which TSA signed
jq -r '.timestamp_authority.tsr_base64' proof.json | base64 -d > token.tsr
# the anchored artefact: batch root if batched, chain hash otherwise
jq -r '.batch_anchor.root // .hashes.chain' proof.json | sed 's/sha256://' | xxd -r -p > anchored.bin
# freetsa.org:
curl -sO https://freetsa.org/files/cacert.pem
curl -sO https://freetsa.org/files/tsa.crt
openssl ts -verify -data anchored.bin -in token.tsr -CAfile cacert.pem -untrusted tsa.crt
# digicert.com or sectigo.com:
openssl ts -verify -data anchored.bin -in token.tsr -CAfile /etc/ssl/certs/ca-certificates.crt
Sigstore Rekor. Rekor receives sha256(anchored_hash_hex) — the SHA-256 of the hex
string, not of the hash itself. Checking that the entry exists is not enough: an entry exists
for every artefact anybody ever logged. What matters is that this entry covers this root.
UUID=$(jq -r '.transparency_log.uuid' proof.json)
curl -s "https://rekor.sigstore.dev/api/v1/log/entries/$UUID" > entry.json
# the logged artefact must be this proof's anchored hash
jq -r '.batch_anchor.root // .hashes.chain' proof.json | sed 's/sha256://' | tr -d '\n' | sha256sum
jq -r '.[].body' entry.json | base64 -d | jq -r '.spec.data.hash.value'
verify_proof.py then checks what a shell one-liner cannot: the entry's own signature, the
signed entry timestamp against the log's public key, and the RFC 6962 inclusion proof against
the log's signed checkpoint. An entry without a valid inclusion proof is a claim, not a log.
Batch inclusion. The walk from your chain hash to the anchored root is RFC 6962 as well:
leaves are sha256(0x00 || chain_hash_bytes), interior nodes sha256(0x01 || left || right),
and an odd node is promoted rather than duplicated. verify_proof.py does this walk and
refuses a path that reaches another root, a wrong leaf_index, or a path carrying more
siblings than the tree shape uses.
Attribution. The key that submits to Rekor is published at GET /v1/pubkey as
rekor_pubkey. Compare it with spec.signature.publicKey.content in the entry. If they
differ, the entry is somebody else's — a valid log entry attributed to the wrong party
proves nothing.
Quota management
Email alerts
ArkForge sends automatic email notifications so your agent never stops silently:
| Trigger | Cooldown | |
|---|---|---|
| Balance drops below 1.00 EUR (~10 proofs) after a debit | "Low credits — action required" + recharge curl | 24h |
| Call rejected due to zero balance (HTTP 402) | "Credits exhausted — agent stopped" + recharge curl | 24h |
| 80% of daily/monthly quota consumed | "Quota alert" + upgrade or recharge hint | Once per threshold |
| First overage proof of the month (opt-in) | "Overage billing active — monthly quota exceeded" | Once per month |
| 80% of overage monthly cap consumed (opt-in) | "Overage alert — 80% of monthly cap used" | Once per threshold |
| Overage cap reached — requests blocked (opt-in) | "Overage cap reached — requests blocked" | Once per cap event |
| Subscription payment failed (Stripe retry pending) | (no email — key stays active during retry period) | — |
| Key suspended after payment exhausted | (key returns HTTP 401 — update card in billing portal) | — |
These emails are sent to the address used during key setup. They include a ready-to-run curl command to recharge or adjust settings immediately — no browser required.
Check quota usage
curl https://trust.arkforge.tech/v1/usage \
-H "X-Api-Key: mcp_pro_xxx..."
Response:
{
"plan": "pro",
"monthly": { "used": 1250, "limit": 5000, "remaining": 3750 },
"credit_balance": 47.5
}
Buy credits (no browser)
After the initial setup, credits can be purchased programmatically — the card saved during checkout is charged automatically.
curl -X POST https://trust.arkforge.tech/v1/credits/buy \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{"amount": 10.00}'
Response:
{
"credits_added": 10.0,
"balance": 57.5,
"proofs_available": 575,
"charge_id": "ch_3T5xyz...",
"receipt_url": "https://pay.stripe.com/receipts/..."
}
Overage billing (opt-in, Pro/Enterprise)
By default, requests beyond your monthly quota are rejected with HTTP 429. You can opt in to overage billing: proofs beyond your quota are debited from your prepaid credits at the per-proof overage rate, up to a monthly cap you choose.
Enable overage billing:
curl -X POST https://trust.arkforge.tech/v1/keys/overage \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{"enabled": true, "cap_eur": 20.00}'
Response:
{
"overage_enabled": true,
"overage_cap_eur": 20.0,
"overage_rate_per_proof": 0.01,
"consent_at": "2026-03-04T15:30:00+00:00",
"message": "Overage billing enabled. Proofs beyond quota billed at 0.01 EUR/proof, cap 20.00 EUR/month."
}
Key parameters:
enabled:trueto opt in,falseto opt out (immediate effect)cap_eur: monthly spending cap for overages (€5–€100, default €20)- Overage proofs are billed from your prepaid credits — not a new charge
- When the cap is reached, requests are blocked (HTTP 429) until you raise the cap or wait for the next month
Check overage status:
curl https://trust.arkforge.tech/v1/keys/overage \
-H "X-Api-Key: mcp_pro_xxx..."
Monitor usage with overage section:
curl https://trust.arkforge.tech/v1/usage \
-H "X-Api-Key: mcp_pro_xxx..."
Response includes an overage section when enabled:
{
"plan": "pro",
"monthly": {"used": 5120, "limit": 5000, "remaining": 0},
"overage": {
"enabled": true,
"cap_eur": 20.0,
"spent_eur": 1.20,
"count": 120,
"remaining_eur": 18.80,
"rate_per_proof": 0.01
}
}
Disable overage billing (effective immediately):
curl -X POST https://trust.arkforge.tech/v1/keys/overage \
-H "X-Api-Key: mcp_pro_xxx..." \
-H "Content-Type: application/json" \
-d '{"enabled": false, "cap_eur": 20.0}'
Consent stored for audit:
consent_at(UTC timestamp) andconsent_rate(rate at time of consent) are preserved in your key metadata even after disabling overage. This provides an auditable record of your billing agreement.
Autonomous agent with budget management
import requests
class AutonomousAgent:
def __init__(self, api_key, min_balance=5.0):
self.api_key = api_key
self.min_balance = min_balance
self.headers = {"X-Api-Key": api_key, "Content-Type": "application/json"}
def ensure_budget(self):
usage = requests.get(
"https://trust.arkforge.tech/v1/usage",
headers=self.headers
).json()
if usage['credit_balance'] < self.min_balance:
requests.post(
"https://trust.arkforge.tech/v1/credits/buy",
headers=self.headers,
json={"amount": 10.0}
)
def execute_task(self, target, payload):
self.ensure_budget()
return requests.post(
"https://trust.arkforge.tech/v1/proxy",
headers=self.headers,
json={"target": target, "payload": payload}
).json()
agent = AutonomousAgent("mcp_pro_xxx...")
result = agent.execute_task("https://provider.com/api", {"task": "analyze"})
Mode comparison
| Mode A | Mode B | |
|---|---|---|
| Proves | Execution only | Execution + payment |
| Spec version | 1.1 | 2.0 |
| Chain hash includes | Request, response, timestamp | + Receipt hash |
| Required key | Free/Pro/Test | Free is sufficient |
| Extra code | None | 3 lines (provider_payment) |
| Use case | Traceability, audit log | Financial audit, compliance |
Pricing
| Plan | Cost | Monthly quota | Key prefix |
|---|---|---|---|
| Free | Free | 500 proofs/month | mcp_free_* |
| Pro | €29/month (+ opt-in overage €0.01/proof) | 5,000 proofs/month | mcp_pro_* |
| Enterprise | €149/month (+ opt-in overage €0.005/proof) | 50,000 proofs/month | mcp_ent_* |
| Test | Stripe test mode | 100 proofs/day | mcp_test_* |
Mode B can use a Free key — certification only, payment is external, no monthly quota consumed.
Integration checklist
Step 1 — Key
- Free (500/month), Pro (€29/month, 5,000), or Enterprise (€149/month, 50,000)?
- Test mode (development) or production?
- Email received with
mcp_xxx...?
Step 2 — Mode
- Mode A (transaction) or Mode B (transaction + payment)?
- If Mode B: Stripe account configured?
Step 3 — Code
- Replace upstream URL with
https://trust.arkforge.tech/v1/proxy - Add
X-Api-Keyheader - Wrap payload:
{"target": "...", "payload": {...}} - If Mode B: add
provider_payment - If Mode C: add
extra_headerswith target API credentials
Step 4 — Test
- Successful call (status 200)?
- Proof generated (
proof_idpresent)? - Public verification works?
Step 5 — Production
- Swap Test key for Pro or Enterprise
- Monitor monthly quota (
GET /v1/usage) - Store proof IDs
Resources
- Full API reference: README.md
- Proof specification: ark-forge/proof-spec
- Quick reference: quick-reference.md
- Support: contact@arkforge.tech
MCP Security Posture Assessment
Analyze an MCP server manifest for security risks and detect changes between deployments.
What it does:
- Flags dangerous capability patterns (code execution, filesystem write, env access, network)
- Detects tool drift: new tools added, tools removed, descriptions changed
- Tracks server version changes across deployments
- Stores a baseline per server — every call updates it
Rate limit: 100 assessments/day per API key.
Quick start
curl -X POST https://trust.arkforge.tech/v1/assess \
-H "X-Api-Key: mcp_xxx..." \
-H "Content-Type: application/json" \
-d '{
"server_id": "my-mcp-server",
"manifest": {
"tools": [
{"name": "read_data", "description": "Read records from database"},
{"name": "write_file", "description": "Write content to a file on disk"}
]
},
"server_version": "1.2.0"
}'
Response
{
"assess_id": "asr_20260403_120000_abc123",
"server_id": "my-mcp-server",
"assessed_at": "2026-04-03T12:00:00+00:00",
"risk_score": 45,
"findings": [
{
"analyzer": "permissions",
"severity": "high",
"tool": "write_file",
"message": "Tool has 'filesystem_write' capability pattern"
}
],
"drift_detected": false,
"drift_summary": {},
"baseline_status": "created"
}
Fields:
risk_score— 0–100. 0 = no findings. Higher = more risk.findings— list of issues found.severity: info | low | medium | high | critical.drift_detected— true if the manifest changed since the last call for thisserver_id.drift_summary—new_tools,removed_tools,changedlists (populated when drift detected).baseline_status—"created"on first call,"updated"on subsequent calls.assess_id— stable identifier for this assessment (future: linkable to proofs).
Drift detection example
import requests
HEADERS = {"X-Api-Key": "mcp_xxx...", "Content-Type": "application/json"}
BASE = "https://trust.arkforge.tech"
def assess(server_id, tools, version=None):
resp = requests.post(f"{BASE}/v1/assess", headers=HEADERS, json={
"server_id": server_id,
"manifest": {"tools": tools},
"server_version": version,
})
return resp.json()
# First call — creates baseline
result = assess("my-server", [{"name": "echo", "description": "Echo input"}], "1.0.0")
print(result["baseline_status"]) # → "created"
print(result["drift_detected"]) # → False
# Second call — adds a new tool
result = assess("my-server", [
{"name": "echo", "description": "Echo input"},
{"name": "exec", "description": "Run a shell command"},
], "1.0.1")
print(result["drift_detected"]) # → True
print(result["drift_summary"]["new_tools"]) # → ["exec"]
EU AI Act Compliance Report
Generate a compliance report mapping your certified proofs to EU AI Act obligations.
What it does:
- Queries all proofs certified under your API key in a date range
- Maps proof fields to specific AI Act articles
- Returns article-level status:
covered|partial|gap|not_applicable - Lists gaps for remediation planning
Applicable to: High-risk AI systems under Annex III (Art. 9, 13, 14, 17) and all AI systems for record-keeping (Art. 22). Art. 10 (data governance) is an organisational obligation — not derivable from transaction proofs.
Quick start
curl -X POST https://trust.arkforge.tech/v1/compliance-report \
-H "X-Api-Key: mcp_xxx..." \
-H "Content-Type: application/json" \
-d '{
"framework": "eu_ai_act",
"date_from": "2026-01-01",
"date_to": "2026-03-31"
}'
Response
{
"report_id": "rpt_20260403_120000_abc123",
"framework": "eu_ai_act",
"framework_version": "1.0",
"date_range": {"from": "2026-01-01T00:00:00+00:00", "to": "2026-03-31T00:00:00+00:00"},
"proof_count": 42,
"articles": [
{
"article": "Art. 9",
"title": "Risk Management System",
"status": "covered",
"evidence": "42 proofs with valid chain hash",
"proof_count": 42,
"proof_sample": ["prf_20260115_...", "prf_20260116_...", "prf_20260117_..."]
},
{
"article": "Art. 10",
"title": "Data and Data Governance",
"status": "not_applicable",
"evidence": "Organisational obligation — not verifiable from transaction proofs",
"reason": "Art. 10 requires data governance policies and dataset documentation.",
"proof_count": 0
},
{
"article": "Art. 13",
"title": "Transparency and Provision of Information",
"status": "covered",
"evidence": "Agent identity, seller, and RFC 3161 timestamp present",
"proof_count": 42
}
],
"gaps": [],
"summary": {"covered": 4, "partial": 0, "gap": 0, "not_applicable": 2}
}
Article coverage
| Article | Title | What proves it |
|---|---|---|
| Art. 9 | Risk Management System | Proofs exist with valid chain hash |
| Art. 10 | Data and Data Governance | Not applicable — organisational obligation |
| Art. 13 | Transparency | agent_identity + seller + verified RFC 3161 timestamp |
| Art. 14 | Human Oversight | agent_identity_verified=true (full) or buyer_fingerprint (partial) |
| Art. 17 | Quality Management System | All proofs pass chain hash integrity verification |
| Art. 22 | Record-keeping | Proof count + timestamp coverage over the period |
Python example
import requests
resp = requests.post(
"https://trust.arkforge.tech/v1/compliance-report",
headers={"X-Api-Key": "mcp_xxx..."},
json={
"framework": "eu_ai_act",
"date_from": "2026-01-01",
"date_to": "2026-03-31",
}
)
report = resp.json()
print(f"Proofs analyzed: {report['proof_count']}")
print(f"Summary: {report['summary']}")
if report["gaps"]:
print(f"Gaps to address: {report['gaps']}")
Notes:
- Only proofs created after v1.3.18 deployment are indexed automatically.
Run
python3 scripts/backfill_proof_index.pyto index pre-existing proofs. date_from/date_toaccept any ISO 8601 format:2026-01-01,2026-01-01T00:00:00Z, etc.- Supported frameworks:
eu_ai_act(default),iso_42001,nist_ai_rmf,soc2_readiness.
ISO/IEC 42001:2023 Compliance Report
Generate a compliance report mapping your certified proofs to ISO/IEC 42001 AI Management System obligations.
What it does:
- Queries all proofs certified under your API key in a date range
- Maps proof fields to ISO 42001 clauses
- Returns clause-level status:
covered|partial|gap|not_applicable - Lists gaps for remediation planning
Applicable to: Organisations implementing or certifying an AI Management System (AIMS) under ISO/IEC 42001:2023.
Quick start
curl -X POST https://trust.arkforge.tech/v1/compliance-report \
-H "X-Api-Key: mcp_xxx..." \
-H "Content-Type: application/json" \
-d '{
"framework": "iso_42001",
"date_from": "2026-01-01",
"date_to": "2026-03-31"
}'
Response
{
"report_id": "rpt_20260403_120000_abc123",
"framework": "iso_42001",
"framework_version": "1.0",
"date_range": {"from": "2026-01-01T00:00:00+00:00", "to": "2026-03-31T00:00:00+00:00"},
"proof_count": 42,
"articles": [
{
"article": "§ 6.1",
"title": "Risk and Opportunity Management",
"status": "covered",
"evidence": "Cryptographic chain hash documents AI action for risk tracking",
"proof_count": 42,
"proof_sample": ["prf_20260115_...", "prf_20260116_...", "prf_20260117_..."]
},
{
"article": "§ 10.1",
"title": "Nonconformity and Corrective Action",
"status": "not_applicable",
"evidence": "Organisational obligation — not verifiable from transaction proofs",
"reason": "§ 10.1 requires documented procedures for identifying and addressing nonconformities.",
"proof_count": 0
}
],
"gaps": [],
"summary": {"covered": 5, "partial": 0, "gap": 0, "not_applicable": 1}
}
Clause coverage
| Clause | Title | What proves it |
|---|---|---|
| § 6.1 | Risk and Opportunity Management | hashes.chain present in proof |
| § 8.2 | AI Risk Assessment | Proof chain hash integrity verifiable |
| § 8.4 | AI System Lifecycle Documentation | spec_version + parties.agent_version both present |
| § 9.1 | Monitoring, Measurement and Evaluation | RFC 3161 verified timestamp (tsa.status == "verified") |
| § 9.2 | Internal Audit | All proofs pass chain hash integrity verification |
| § 10.1 | Nonconformity and Corrective Action | Not applicable — organisational obligation |
Python example
import requests
resp = requests.post(
"https://trust.arkforge.tech/v1/compliance-report",
headers={"X-Api-Key": "mcp_xxx..."},
json={
"framework": "iso_42001",
"date_from": "2026-01-01",
"date_to": "2026-03-31",
}
)
report = resp.json()
print(f"Proofs analyzed: {report['proof_count']}")
print(f"Summary: {report['summary']}")
if report["gaps"]:
print(f"Gaps to address: {report['gaps']}")
Notes:
- Only proofs created after v1.3.18 deployment are indexed automatically.
Run
python3 scripts/backfill_proof_index.pyto index pre-existing proofs. date_from/date_toaccept any ISO 8601 format:2026-01-01,2026-01-01T00:00:00Z, etc.- § 8.4 requires
agent_versionin proofparties. Agents that do not report their version will yieldpartialstatus on this clause.
NIST AI RMF 1.0 Compliance Report
Generate a compliance report mapping your certified proofs to NIST AI Risk Management Framework subcategories.
Applicable to: Organisations implementing the NIST AI RMF across the four core functions: GOVERN, MAP, MEASURE, MANAGE.
Quick start
curl -X POST https://trust.arkforge.tech/v1/compliance-report \
-H "X-Api-Key: mcp_xxx..." \
-H "Content-Type: application/json" \
-d '{
"framework": "nist_ai_rmf",
"date_from": "2026-01-01",
"date_to": "2026-03-31"
}'
Subcategory coverage
| ID | Function | Title | What proves it |
|---|---|---|---|
| GOVERN 1.1 | GOVERN | AI Risk Policies and Procedures | Not applicable — organisational obligation |
| MAP 1.1 | MAP | AI System Context Established | spec_version + parties.agent_identity |
| MAP 5.2 | MAP | AI Risk Tracking Practices | hashes.chain present |
| MEASURE 1.1 | MEASURE | Risk Measurement Methods | Proof chain hash integrity verifiable |
| MEASURE 2.5 | MEASURE | AI System Performance Monitored | RFC 3161 verified timestamp |
| MANAGE 1.3 | MANAGE | Risk Treatment Documented | Chain hash + integrity verified |
| MANAGE 4.1 | MANAGE | Risk Monitoring Established | proof_id + timestamp present |
Reference: NIST AI 100-1 (2023) — https://doi.org/10.6028/NIST.AI.100-1
SOC 2 Readiness Report
Generate a SOC 2 readiness evidence report mapping your certified proofs to AICPA Trust Service Criteria.
Important: This report produces readiness evidence, not a formal SOC 2 audit opinion. A SOC 2 Type II report requires an independent CPA firm accredited by the AICPA. Use this report to prepare for an audit, not to replace one.
Applicable to: SaaS organisations preparing for a SOC 2 Type II audit, particularly around processing integrity and security controls.
Quick start
curl -X POST https://trust.arkforge.tech/v1/compliance-report \
-H "X-Api-Key: mcp_xxx..." \
-H "Content-Type: application/json" \
-d '{
"framework": "soc2_readiness",
"date_from": "2026-01-01",
"date_to": "2026-03-31"
}'
Criteria coverage
| Criterion | Category | Title | What proves it |
|---|---|---|---|
| CC6.1 | Security | Logical Access Controls | buyer_fingerprint + seller in proof |
| CC6.7 | Security | Transmission and Movement Integrity | hashes.request + hashes.response + hashes.chain |
| CC7.2 | Security | Security Event Monitoring | RFC 3161 verified timestamp |
| PI1.1 | Processing Integrity | Completeness | Proof chain hash integrity verifiable |
| PI1.2 | Processing Integrity | Accuracy | proof_id + timestamp + certification_fee.status == "succeeded" |
| A1.1 | Availability | Availability Monitoring | Not applicable — infrastructure obligation |
Reference: AICPA Trust Services Criteria (2017, updated 2022)
Proof Index — Operations & Resilience
The proof index powers date-range queries for compliance reports. Understanding its resilience model is useful for operators.
How it works
When Redis is available, the service uses DualWrite mode: every proof is written to both the JSONL file (data/proof_index.jsonl) and Redis. The JSONL is written first and is always committed. Redis is written second and used for fast queries.
When Redis is unavailable, the service falls back to File-only mode: proofs are written to the JSONL only. Queries scan the JSONL (adequate for <100k proofs).
Automatic reconciliation
The service reconciles the JSONL → Redis automatically in two ways:
| Trigger | When | What it does |
|---|---|---|
| Startup | Every service (re)start | Replays the full JSONL into Redis |
| Periodic | Every 5 minutes | Replays the last 25 hours of JSONL into Redis |
This means:
- Redis restart → reconciled on next service restart or within 5 minutes
- Redis outage during runtime → proofs written to JSONL only during outage, replayed into Redis automatically within 5 minutes of Redis recovery
Manual reconciliation
# Rebuild Redis from JSONL (after Redis restart)
python3 scripts/backfill_proof_index.py --from-jsonl
# Only replay the last 24 hours
python3 scripts/backfill_proof_index.py --from-jsonl --since 2026-04-02T00:00:00Z
# Dry run
python3 scripts/backfill_proof_index.py --from-jsonl --dry-run
# Full backfill from proof files (first deploy or data migration)
python3 scripts/backfill_proof_index.py
Source of truth
JSONL is always the source of truth. Redis is derived and can be rebuilt from the JSONL at any time. Never delete data/proof_index.jsonl.
CTEF Constraint Evaluation — Cross-Implementation Reference
ArkForge Trust Layer participates in the Composable Trust Evidence Format (CTEF) working group as an enforcement gateway: it consumes constraint evaluation evidence from providers like AgentGraph and enforces verdicts at the trust boundary.
Field mapping: AgentGraph → ArkForge
| AgentGraph CTEF field | ArkForge attestation slot | Description |
|---|---|---|
claim_type | constraint_evaluation | CTEF continuity-layer claim type |
facet | Dimension under evaluation | e.g. budget_remaining, token_limit, response_time_ms |
limit | Authority-declared ceiling | Extracted from the authority chain at issuance |
actual | Observed value at evaluation time | Runtime measurement |
delta | limit - actual | Positive = within bounds, zero = boundary, negative = exceeded |
authority_chain_hash | Chain integrity anchor | JCS-canonical hash with all nested proof fields stripped (depth-first) |
authority_chain_ref | URI to the authority chain | Allows independent re-verification |
Enforcement gate: no_critical_findings
The binary enforcement-time decision is derived from the delta sign:
| Delta | Verdict | Enforcement action |
|---|---|---|
delta > threshold | pass | Request proceeds |
0 < delta ≤ threshold | pass (near-miss) | Request proceeds, monitoring alert fires |
delta ≤ 0 | fail | Request blocked at enforcement gateway |
The no_critical_findings boolean is the canonical gate shape: true when all evaluated facets have delta > 0, false otherwise.
Canonical test vectors (from PR #14)
Three constraint_evaluation scenarios shipped as byte-match-validated CTEF vectors:
| Scenario | Delta | Operational meaning |
|---|---|---|
within_limit | +250 | Agent well within authority bounds |
near_miss | +5 | Monitoring fires before constraint breaks |
exceeded | -150 | Enforcement gateway blocks the request |
Self-verification: python3 specs/test-vectors/verify_execution_attestation_arkforge.py
Composable envelope: tier_upgrade_proof
A single CTEF envelope can carry both a static posture gate (scan results) and a runtime authorization decision. The tier_upgrade_proof shape composes constraint_evaluation evidence with scan posture to produce a combined verdict:
{
"claim_type": "tier_upgrade_proof",
"posture_gate": { "scan_type": "security_posture", "no_critical_findings": true },
"constraint_evaluation": { "facet": "tier_ceiling", "limit": 3, "actual": 2, "delta": 1 },
"verdict": "granted",
"requester_did": "did:web:agent.example.com",
"policy_ref": "AEP-0.1.1/§4"
}
Three test vectors cover the decision space: granted (posture passes + within ceiling), denied (posture fails or ceiling exceeded), replay_vulnerable (stale posture evidence reused past validity window).
Interop status: ArkForge is the 6th byte-match-validated CTEF implementation and the 1st enforcement-gateway-role entry in the AgentGraph interop harness. Cross-validated against AgentGraph CTEF v0.3.1 (agentgraph-co/agentgraph@69ad94d).
- Quick reference: quick-reference.md
- Support: contact@arkforge.tech
Last updated: 2026-05-02