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/buy triggers 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_url in the response is a verifiable Stripe receipt for the recharge

Summary:

StepWhoAction
Initial setupHumanOpen Stripe Checkout, subscribe (Pro €29/month or Enterprise €149/month)
Execute tasksAgentPOST /v1/proxy — included in monthly quota
Overage credits (opt-in)AgentPOST /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

FieldContent
tool_nameValue of X-Agent-Identity header
request_hashSHA-256 of the exact payload sent
response_hashSHA-256 of the exact response received
timestampRFC 3161 certified (independent TSA)
signatureEd25519, verifiable with ArkForge public key
rekor_log_idEntry 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:

  1. Open checkout_url in a browser
  2. Test card: 4242 4242 4242 4242
  3. Expiry: any future date (e.g. 12/30)
  4. CVC: any 3 digits (e.g. 123)
  5. 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:

  1. Open checkout_url in a browser
  2. Enter your real card details
  3. Confirm → €29/month subscription started
  4. 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:

EventWhat happens
Subscription startedAPI key created and emailed
Monthly renewalKey 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 paidKey automatically reactivated — no action required
Subscription cancelledKey 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:

  1. Trust Layer forwards the request to the provider
  2. Trust Layer hashes request + response (SHA-256)
  3. Trust Layer generates a proof with Ed25519 signature
  4. 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:

  1. Agent pays provider via Stripe (5.00 EUR)
  2. Agent receives a receipt URL from Stripe
  3. Agent calls Trust Layer with provider_payment
  4. Trust Layer independently fetches the receipt from Stripe
  5. Trust Layer hashes the receipt (SHA-256)
  6. Trust Layer includes the receipt hash in the chain
  7. 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 methodKey field in DID DocumentAccepted formatNotes
did:webpublicKeyMultibasebase58btc, prefix zWith or without multicodec prefix 0xed01 — both accepted
did:webpublicKeyJwkkty: OKP, crv: Ed25519, x: <base64url>Standard JWK, padding optional
did:keyEncoded in the DID itselfbase58btc + multicodec 0xed01 (required by spec)Decoded from did:key:z<multibase> directly
did:oatrpublic_key in registry manifestraw base64url, no paddingDirect 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

LimitValueError when exceeded
Target protocolHTTPS onlyinvalid_target 400
MethodsGET or POSTinvalid_request 400
Payload formatJSON onlyinvalid_request 400
Response timeout120 secondsproxy_timeout 504 — proof still issued
Response stored / hashed1 MB maxTruncated at 1 MB — proof covers truncated content
Daily capNone for Free/Pro/Enterprise — full quota usable at any time. Test keys: 100/dayrate_limited 429 (test keys only)
Monthly quota500 / 5 000 / 50 000 (Free / Pro / Enterprise)rate_limited 429 (unless overage enabled)
extra_headers countMax 10invalid_request 400
extra_headers value lengthMax 4 096 charsinvalid_request 400

Unsupported by the proxy:

FeatureStatus
Binary payloads (multipart/form-data, raw bytes)Not supported — JSON only
Streaming / Server-Sent EventsNot supported — full response collected before proof
WebSocket (Upgrade header)Not supported — blocked
HTTP (non-TLS) targetsNot supported — HTTPS required
Private IPs / localhostBlocked — 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

WitnessCan ArkForge produce it alone?
1Chain hash recomputationYes — whoever fabricates a proof produces coherent hashes. Self-consistency only.
2Ed25519 signatureYes — the signer is ArkForge. It proves issuance, not truth.
3Batch inclusion proofYes — self-consistency again, but it is what carries witnesses 4 and 5 down to this individual proof.
4RFC 3161 timestampNo — an independent Timestamp Authority signed this hash at a point in time.
5Sigstore Rekor entryNo — 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 fieldIn the public proof
transaction_id, buyer_fingerprintHidden. Only the commitment is published.
request_hash, response_hashIn clear, as hashes.request and hashes.response.
timestamp, seller, upstream_timestamp, receipt_content_hashIn clear.
agent_identity, agent_identity_verified, did_resolution_status, identity_consistentIn 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:

TriggerEmailCooldown
Balance drops below 1.00 EUR (~10 proofs) after a debit"Low credits — action required" + recharge curl24h
Call rejected due to zero balance (HTTP 402)"Credits exhausted — agent stopped" + recharge curl24h
80% of daily/monthly quota consumed"Quota alert" + upgrade or recharge hintOnce 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: true to opt in, false to 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) and consent_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 AMode B
ProvesExecution onlyExecution + payment
Spec version1.12.0
Chain hash includesRequest, response, timestamp+ Receipt hash
Required keyFree/Pro/TestFree is sufficient
Extra codeNone3 lines (provider_payment)
Use caseTraceability, audit logFinancial audit, compliance

Pricing

PlanCostMonthly quotaKey prefix
FreeFree500 proofs/monthmcp_free_*
Pro€29/month (+ opt-in overage €0.01/proof)5,000 proofs/monthmcp_pro_*
Enterprise€149/month (+ opt-in overage €0.005/proof)50,000 proofs/monthmcp_ent_*
TestStripe test mode100 proofs/daymcp_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-Key header
  • Wrap payload: {"target": "...", "payload": {...}}
  • If Mode B: add provider_payment
  • If Mode C: add extra_headers with target API credentials

Step 4 — Test

  • Successful call (status 200)?
  • Proof generated (proof_id present)?
  • Public verification works?

Step 5 — Production

  • Swap Test key for Pro or Enterprise
  • Monitor monthly quota (GET /v1/usage)
  • Store proof IDs

Resources


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 this server_id.
  • drift_summarynew_tools, removed_tools, changed lists (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

ArticleTitleWhat proves it
Art. 9Risk Management SystemProofs exist with valid chain hash
Art. 10Data and Data GovernanceNot applicable — organisational obligation
Art. 13Transparencyagent_identity + seller + verified RFC 3161 timestamp
Art. 14Human Oversightagent_identity_verified=true (full) or buyer_fingerprint (partial)
Art. 17Quality Management SystemAll proofs pass chain hash integrity verification
Art. 22Record-keepingProof 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.py to index pre-existing proofs.
  • date_from / date_to accept 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

ClauseTitleWhat proves it
§ 6.1Risk and Opportunity Managementhashes.chain present in proof
§ 8.2AI Risk AssessmentProof chain hash integrity verifiable
§ 8.4AI System Lifecycle Documentationspec_version + parties.agent_version both present
§ 9.1Monitoring, Measurement and EvaluationRFC 3161 verified timestamp (tsa.status == "verified")
§ 9.2Internal AuditAll proofs pass chain hash integrity verification
§ 10.1Nonconformity and Corrective ActionNot 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.py to index pre-existing proofs.
  • date_from / date_to accept any ISO 8601 format: 2026-01-01, 2026-01-01T00:00:00Z, etc.
  • § 8.4 requires agent_version in proof parties. Agents that do not report their version will yield partial status 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

IDFunctionTitleWhat proves it
GOVERN 1.1GOVERNAI Risk Policies and ProceduresNot applicable — organisational obligation
MAP 1.1MAPAI System Context Establishedspec_version + parties.agent_identity
MAP 5.2MAPAI Risk Tracking Practiceshashes.chain present
MEASURE 1.1MEASURERisk Measurement MethodsProof chain hash integrity verifiable
MEASURE 2.5MEASUREAI System Performance MonitoredRFC 3161 verified timestamp
MANAGE 1.3MANAGERisk Treatment DocumentedChain hash + integrity verified
MANAGE 4.1MANAGERisk Monitoring Establishedproof_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

CriterionCategoryTitleWhat proves it
CC6.1SecurityLogical Access Controlsbuyer_fingerprint + seller in proof
CC6.7SecurityTransmission and Movement Integrityhashes.request + hashes.response + hashes.chain
CC7.2SecuritySecurity Event MonitoringRFC 3161 verified timestamp
PI1.1Processing IntegrityCompletenessProof chain hash integrity verifiable
PI1.2Processing IntegrityAccuracyproof_id + timestamp + certification_fee.status == "succeeded"
A1.1AvailabilityAvailability MonitoringNot 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:

TriggerWhenWhat it does
StartupEvery service (re)startReplays the full JSONL into Redis
PeriodicEvery 5 minutesReplays 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 fieldArkForge attestation slotDescription
claim_typeconstraint_evaluationCTEF continuity-layer claim type
facetDimension under evaluatione.g. budget_remaining, token_limit, response_time_ms
limitAuthority-declared ceilingExtracted from the authority chain at issuance
actualObserved value at evaluation timeRuntime measurement
deltalimit - actualPositive = within bounds, zero = boundary, negative = exceeded
authority_chain_hashChain integrity anchorJCS-canonical hash with all nested proof fields stripped (depth-first)
authority_chain_refURI to the authority chainAllows independent re-verification

Enforcement gate: no_critical_findings

The binary enforcement-time decision is derived from the delta sign:

DeltaVerdictEnforcement action
delta > thresholdpassRequest proceeds
0 < delta ≤ thresholdpass (near-miss)Request proceeds, monitoring alert fires
delta ≤ 0failRequest 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:

ScenarioDeltaOperational meaning
within_limit+250Agent well within authority bounds
near_miss+5Monitoring fires before constraint breaks
exceeded-150Enforcement 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).



Last updated: 2026-05-02