How to implement a conformant OpenChainGraph tool, emit a verifiable artifact, self-certify using the conformance suite, and display the Powered by OpenChainGraph badge.
OpenChainGraph is an open standard for verifiable, composable decision artifacts produced by fintech tools. Every conformant tool takes a policy_parameters object as input, runs a deterministic pure-function kernel, and emits a JSON artifact that includes a cryptographic execution_hash committing to both the inputs and the output.
Version 0.4 adds Compute Binding: tools can run server-side (via the MCP Apps server at mcp.ainumbers.co/mcp) and return their full artifact — including the hash — directly to an AI agent. The agent, a regulator, or any third party can then verify the hash client-side using the public canonicalization algorithm without trusting any intermediary.
The standard is maintained by Post Oak Labs and published at ainumbers.co/chaingraph/openchain-graph-spec.html. All reference kernels are open-source under CC BY 4.0.
Every OpenChainGraph artifact must include these top-level fields:
| Field | Type | Req? | Description |
|---|---|---|---|
| @context | string | required | JSON-LD context URI — use https://ainumbers.co/chaingraph/context/v0.3/context.jsonld |
| chaingraph_version | string | required | Spec version — "0.4.0" |
| tool_id | string | required | Stable slug identifying the tool (e.g. "art-09-dora-incident-classifier") |
| mandate_type | string | required | Semantic type tag (e.g. "infrastructure_mandate", "payment_mandate", "compliance_mandate") |
| policy_parameters | object | required | The exact inputs passed to compute(). Part of the hash preimage. |
| output_payload | object | required | The exact outputs returned by compute(). Part of the hash preimage. |
| execution_hash | string | required | SHA-256 of the canonical preimage. Format: "sha256:<64-hex-chars>" |
| chain | object | required | { parent_hashes: string[], parent_tool_ids: string[], chain_depth: number } |
| generated_at | string | optional | ISO 8601 timestamp of artifact generation (not part of hash preimage) |
| compute_mode | string | optional | "server" or "client" |
| compliance_flags | string[] | optional | Machine-readable compliance outcome codes |
| tool_version | string | optional | Semver of the tool kernel at time of emission |
| audit_signature | object | optional | JWS / placeholder for audit attestation (payloadType, payload, signatures) |
Only policy_parameters and output_payload are hashed. All other envelope fields (generated_at, chain, audit_signature, etc.) are framing and excluded from the hash preimage — they may be added by the caller without invalidating the hash.
The canonicalization algorithm is RFC 8785 (JSON Canonicalization Scheme) over the I-JSON subset. The exact steps:
{ policy_parameters, output_payload }JSON.stringify(canonicalized) with no spacing arguments. This produces the canonical preimage string."sha256:" in the artifact.// Recursively sort object keys (RFC 8785 / JCS — I-JSON subset) function cgCanon(v) { if (Array.isArray(v)) return v.map(cgCanon); if (v && typeof v === 'object') return Object.keys(v).sort().reduce((o, k) => { o[k] = cgCanon(v[k]); return o; }, {}); return v; } // The canonical string that gets hashed function canonicalPreimage(policy_parameters, output_payload) { return JSON.stringify(cgCanon({ policy_parameters, output_payload })); } // Returns lowercase hex (64 chars) — no prefix async function executionHash(policy_parameters, output_payload) { const bytes = new TextEncoder().encode(canonicalPreimage(policy_parameters, output_payload)); const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes); return Array.from(new Uint8Array(digest)) .map(b => b.toString(16).padStart(2, '0')).join(''); } // Usage const hash = await executionHash(policy_parameters, output_payload); artifact.execution_hash = `sha256:${hash}`;
This function is available in all environments that expose globalThis.crypto.subtle (browsers, Cloudflare Workers, Node 18+). The reference implementation lives at chaingraph/kernels/_hash.mjs in the AINumbers repo (CC BY 4.0).
The AINumbers conformance suite lives at chaingraph/conformance/ and provides three reference test vectors (art-01, art-09, art-34) plus a Node.js runner. To self-certify your own tool:
node chaingraph/conformance/run.mjs. All three reference fixtures must pass — this proves your hash implementation is compatible with the standard.conformance/vectors/your-tool-id.fixture.json following the fixture schema (see conformance/README.md). Set expected_execution_hash to "sha256:COMPUTE_ON_FIRST_RUN".node run.mjs to check your expected_output_payload matches kernel output. Then run node run.mjs --update to pin the real hash into the fixture file.Once your tool emits conformant artifacts, display the badge in your README or documentation:
<img src="https://ainumbers.co/chaingraph/badge-ocg.svg" alt="Powered by OpenChainGraph" width="180" height="20">
Or link the badge to the integration guide: <a href="https://ainumbers.co/chaingraph/ocg-integration-guide.html"><img src="https://ainumbers.co/chaingraph/badge-ocg.svg" …></a>
Suppose you have a KYC screening tool with tool_id my-kyc-checker. Its compute() function takes a customer risk profile and returns a risk tier.
// my-kyc-checker.kernel.mjs export function compute(pp) { const score = (pp.pep ? 30 : 0) + (pp.sanctions_hit ? 50 : 0) + (pp.high_risk_jurisdiction ? 20 : 0); const tier = score >= 50 ? 'high' : score >= 20 ? 'medium' : 'low'; return { output_payload: { risk_score: score, risk_tier: tier }, compliance_flags: [`KYC_TIER_${tier.toUpperCase()}`], }; }
import { compute } from './my-kyc-checker.kernel.mjs'; import { executionHash } from './chaingraph/kernels/_hash.mjs'; const pp = { entity_name: 'ACME Corp', pep: false, sanctions_hit: false, high_risk_jurisdiction: true, }; const { output_payload, compliance_flags } = compute(pp); const hash = await executionHash(pp, output_payload); const artifact = { '@context': 'https://ainumbers.co/chaingraph/context/v0.3/context.jsonld', chaingraph_version: '0.4.0', tool_id: 'my-kyc-checker', mandate_type: 'compliance_mandate', generated_at: new Date().toISOString(), execution_hash: `sha256:${hash}`, chain: { parent_hashes: [], parent_tool_ids: [], chain_depth: 0 }, policy_parameters: pp, output_payload, compliance_flags, };
{
"@context": "https://ainumbers.co/chaingraph/context/v0.3/context.jsonld",
"chaingraph_version": "0.4.0",
"tool_id": "my-kyc-checker",
"mandate_type": "compliance_mandate",
"generated_at": "2026-06-19T12:00:00.000Z",
"execution_hash": "sha256:<64-hex-chars>",
"chain": { "parent_hashes": [], "parent_tool_ids": [], "chain_depth": 0 },
"policy_parameters": {
"entity_name": "ACME Corp", "pep": false,
"sanctions_hit": false, "high_risk_jurisdiction": true
},
"output_payload": { "risk_score": 20, "risk_tier": "medium" },
"compliance_flags": ["KYC_TIER_MEDIUM"]
}
Paste this artifact into verify.html to confirm the hash verifies. Replace "sha256:<64-hex-chars>" with the actual computed value before verifying.
All links open on ainumbers.co — no external dependencies.