Regulated audit trails
Your agent writes these files into openspec/changes/integrate-aisot-audit-trail/ in your repo, runs openspec validate integrate-aisot-audit-trail, and works the task list. The setup guide explains each step for people.
language: http · python · typescript with: CommitOps · RepoOps
OPENSPEC/CHANGES/INTEGRATE-AISOT-AUDIT-TRAIL/PROPOSAL.MD
# Proposal: Regulated audit trails ## Why Audit logs kept by the company being audited can be rewritten. Anchoring each AI decision's inputs and outputs by hash in an outside, append-only log fixes the method at the time of the decision. ## What Changes - Each AI decision or output gets a record by hash: inputs, model, method version. - Records sit under a daily root the company cannot edit, with outside timestamps. - Auditors receive an export bundle and check it offline. ## Capabilities ### New Capabilities - `audit-trail`: Fix the method at decision time and hand auditors a bundle they check offline. ## Impact - External service: AI Source of Truth at https://aisourceoftruth.com. Reads are anonymous. Writes need a publisher key pair you generate, a client id and secret from registration, and a token from `/oauth/token`. - Routes used: `POST /v1/records`, `POST /v1/export`, `GET /v1/export/verify.mjs`. - No PromptReports product is required. - Source of this plan: https://aisourceoftruth.com/use-cases/regulated-audit-trail (CC0). Regenerate it with `curl -H 'Accept: application/json' 'https://aisourceoftruth.com/use-cases/regulated-audit-trail/openspec?lang=typescript'`.
OPENSPEC/CHANGES/INTEGRATE-AISOT-AUDIT-TRAIL/DESIGN.MD
# Design: Regulated audit trails
## Context
Companies under the EU AI Act, NIST AI RMF or sector rules, and the auditors who review them. Audit logs kept by the company being audited can be rewritten. Anchoring each AI decision's inputs and outputs by hash in an outside, append-only log fixes the method at the time of the decision.
## Where it sits in your agent
Publish from the service that commits decisions, inside its write path, with a retry queue so a registry outage delays the record instead of losing it.
Over MCP, point the agent at `https://aisourceoftruth.com/mcp` (Streamable HTTP, JSON-RPC 2.0; read tools need no token). The tools this change uses: `publish_record`, `export_bundle`.
## Decisions
- Treat the registry as a source of facts about records, never about the world. Show what it says, with the digest, and do not turn a verdict into a quality score.
- If the registry cannot be reached, treat the answer as unknown (the same as `check`), never as `rely`, and retry later.
- Keep content private: send hashes. Never put personal data in a record; it is public and permanent.
- Keep the private key and client secret in your secret store. Rotate keys at `POST /v1/keys`; a revoked key cannot sign.
## Setup and signing helper
```ts
// Node 20 or later: WebCrypto signs Ed25519 with no dependency.
const AISOT = process.env.AISOT_ORIGIN ?? "https://aisourceoftruth.com";
const key = await crypto.subtle.importKey("jwk", JSON.parse(process.env.AISOT_PRIVATE_JWK!), { name: "Ed25519" }, false, ["sign"]);
async function token(): Promise<string> {
const r = await fetch(`${AISOT}/oauth/token`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ grant_type: "client_credentials", client_id: process.env.AISOT_CLIENT_ID, client_secret: process.env.AISOT_CLIENT_SECRET }),
});
if (!r.ok) throw new Error(`token: ${r.status}`);
return (await r.json()).access_token; // lasts 60 minutes
}
async function signedPost(path: string, body: unknown, contentType = "application/json"): Promise<any> {
const raw = new TextEncoder().encode(JSON.stringify(body));
const sig = Buffer.from(await crypto.subtle.sign("Ed25519", key, raw)).toString("base64url");
const r = await fetch(`${AISOT}${path}`, {
method: "POST",
headers: {
"content-type": contentType,
authorization: `Bearer ${await token()}`,
"x-aisot-key-id": process.env.AISOT_KEY_ID!,
"x-aisot-signature": sig,
"idempotency-key": crypto.randomUUID(),
},
body: raw,
});
if (!r.ok) throw new Error(`${path}: ${r.status} ${await r.text()}`);
return r.json();
}
```
## Calls
### POST /v1/records
append a signed record; the answer carries its digest and inclusion proof.
```ts
const answer = await signedPost("/v1/records", record, "application/vnd.aisot.record+json");
```
### POST /v1/export
an offline bundle with proofs and timestamps for up to 100 records.
```ts
const answer = await (await fetch(`${AISOT}/v1/export`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({"digests":["{digest}"]}),
})).json();
```
### GET /v1/export/verify.mjs
the offline verifier an auditor runs on a bundle.
```ts
const answer = await (await fetch(`${AISOT}/v1/export/verify.mjs`, { headers: { accept: "application/json" } })).json();
```
## What this does not prove
- Reference material, not legal advice. The record shows what you recorded and when, not that the decision was right.
OPENSPEC/CHANGES/INTEGRATE-AISOT-AUDIT-TRAIL/TASKS.MD
# Tasks: Regulated audit trails ## 1. Configure - [ ] 1.1 Set `AISOT_ORIGIN=https://aisourceoftruth.com` in the agent's environment. - [ ] 1.2 Generate an Ed25519 key pair, register the publisher (`POST /v1/publishers`), and store `AISOT_CLIENT_ID`, `AISOT_CLIENT_SECRET`, `AISOT_KEY_ID` and the private key as secrets. - [ ] 1.3 Mint a token at `/oauth/token` and confirm `GET /v1/me` answers with your publisher id. - [ ] 1.4 Optional: add the MCP server `https://aisourceoftruth.com/mcp` to the agent's MCP config. ## 2. Build - [ ] 2.1 Choose what to anchor: Map each decision to a record: the output's hash as the artifact, input hashes in `inputs`, the model in `model`. See /guides/ai-act-nist for the field mapping. - [ ] 2.2 Publish at decision time: Publish the record in the same transaction that commits the decision. Keep personal data out: hashes only. (`POST /v1/records`) - [ ] 2.3 Hand over a bundle: Export up to 100 records per bundle for the audit period. (`POST /v1/export`) - [ ] 2.4 Give auditors the verifier: Auditors download verify.mjs and run it themselves. (`GET /v1/export/verify.mjs`) ## 3. Verify against the live registry - [ ] 3.1 Choose what to anchor: The mapping is written down and reviewed. - [ ] 3.2 Publish at decision time: Every decision row has a record digest. - [ ] 3.3 Hand over a bundle: The bundle verifies offline. - [ ] 3.4 Give auditors the verifier: An auditor reproduces the check without contacting you. ## 4. Done when - [ ] 4.1 Each decision has a record fixed at decision time. - [ ] 4.2 Auditors check the trail offline without trusting your systems.
OPENSPEC/CHANGES/INTEGRATE-AISOT-AUDIT-TRAIL/SPECS/AUDIT-TRAIL/SPEC.MD
# audit-trail Specification ## ADDED Requirements ### Requirement: Decisions are anchored The decision service SHALL publish a hashes-only record for each AI decision at the time it commits the decision. #### Scenario: Decision committed - **WHEN** a decision is committed - **THEN** a record for it exists or is queued for retry ### Requirement: Auditors verify offline The audit export SHALL be a registry bundle that verifies with verify.mjs. #### Scenario: Audit request - **WHEN** an auditor asks for a period - **THEN** they receive bundles that verify offline
$ curl -H 'Accept: application/json' https://aisourceoftruth.com/use-cases/regulated-audit-trail/openspec?lang=typescript # this page is a rendering of the API. Reproduce it as data.