Attribute published AI content
Your agent writes these files into openspec/changes/integrate-aisot-content-attribution/ in your repo, runs openspec validate integrate-aisot-content-attribution, and works the task list. The setup guide explains each step for people.
language: http · python · typescript with: CommitOps · PromptReports
OPENSPEC/CHANGES/INTEGRATE-AISOT-CONTENT-ATTRIBUTION/PROPOSAL.MD
# Proposal: Attribute published AI content
## Why
Readers, search engines and other agents cannot tell how a piece was made or whether it changed since. A signed record per piece, linked from the page, answers both and makes corrections public.
## What Changes
- At publish time, the site hashes the final text and posts a provenance record with the model and editors.
- The page embeds the badge and a `<link rel="alternate">` that points agents at the record.
- A correction is an erratum on the record, never a silent edit.
- Add CommitOps: If a post announces a shipped feature, link its record to the matching promise in CommitOps.
## Capabilities
### New Capabilities
- `content-attribution`: Publish a provenance record per post, with model and editors, and embed the badge next to it.
## 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/errata`, `GET /r/{digest}?format=json`.
- PromptReports products: CommitOps (optional; the change works without them).
- Source of this plan: https://aisourceoftruth.com/use-cases/attribute-published-content (CC0). Regenerate it with `curl -H 'Accept: application/json' 'https://aisourceoftruth.com/use-cases/attribute-published-content/openspec?lang=http&with=commitops'`.
OPENSPEC/CHANGES/INTEGRATE-AISOT-CONTENT-ATTRIBUTION/DESIGN.MD
# Design: Attribute published AI content
## Context
Newsrooms, blogs, docs sites and content platforms that publish AI-assisted work. Readers, search engines and other agents cannot tell how a piece was made or whether it changed since. A signed record per piece, linked from the page, answers both and makes corrections public.
## Where it sits in your agent
Hook the publish step of your CMS or static site build, after the final render. Fail the build if the record is refused, so no page ships without one.
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`, `resolve_record`.
## 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
```sh
# Publisher setup, once. Make an Ed25519 key pair yourself; the registry never sees the private key.
# 1. Register: POST https://aisourceoftruth.com/v1/publishers with {"id","name","public_jwk"}, signed by that key
# (X-AISOT-Signature over the exact body bytes, base64url) and an Idempotency-Key.
# The answer carries client_id, client_secret and your key_id.
# 2. Token (60 minutes; mint another the same way):
curl -s -X POST 'https://aisourceoftruth.com/oauth/token' -H 'Content-Type: application/json' \
--data '{"grant_type":"client_credentials","client_id":"'"$AISOT_CLIENT_ID"'","client_secret":"'"$AISOT_CLIENT_SECRET"'"}'
# ./sign-ed25519 reads bytes on stdin and prints the base64url Ed25519 signature.
```
## Calls
### POST /v1/records
append a signed record; the answer carries its digest and inclusion proof.
```sh
BODY='{ ...record }'
SIG=$(printf %s "$BODY" | ./sign-ed25519) # base64url signature over these exact bytes
curl -s -X POST 'https://aisourceoftruth.com/v1/records' \
-H "Authorization: Bearer $AISOT_TOKEN" \
-H "X-AISOT-Key-Id: $AISOT_KEY_ID" \
-H "X-AISOT-Signature: $SIG" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/vnd.aisot.record+json' \
--data-binary "$BODY"
```
### POST /v1/errata
append a public correction to one of your records.
```sh
BODY='{"record_digest":"{digest}","correction":"{what changed and why}"}'
SIG=$(printf %s "$BODY" | ./sign-ed25519) # base64url signature over these exact bytes
curl -s -X POST 'https://aisourceoftruth.com/v1/errata' \
-H "Authorization: Bearer $AISOT_TOKEN" \
-H "X-AISOT-Key-Id: $AISOT_KEY_ID" \
-H "X-AISOT-Signature: $SIG" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
--data-binary "$BODY"
```
### GET /r/{digest}?format=json
one record: publisher, key, model, log position and verdict.
```sh
curl -s -H 'Accept: application/json' 'https://aisourceoftruth.com/r/{digest}?format=json'
```
## With the PromptReports products
- **CommitOps** (https://aisourceoftruth.com/p/commitops) keeps the ledger of what a company promised its customers, and can tie a promise to the record that shows it shipped. In this change: If a post announces a shipped feature, link its record to the matching promise in CommitOps.
## What this does not prove
- The record carries what you state. Readers see it as your statement, labeled ATTESTED.
OPENSPEC/CHANGES/INTEGRATE-AISOT-CONTENT-ATTRIBUTION/TASKS.MD
# Tasks: Attribute published AI content
## 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 Hash the published text: Hash the exact bytes you serve, such as the rendered markdown or HTML body, and keep the method fixed so readers can reproduce it.
- [ ] 2.2 Publish the record: Post a provenance record with `artifact` (digest, type, version, title, url), `model`, `created_at` and `edited_by` for the editors. The answer carries the record digest and its inclusion proof. (`POST /v1/records`)
- [ ] 2.3 Embed the badge: Place the record's embed snippet (from the record page or `registry.embed_html` in its JSON) next to the article. (`GET /r/{digest}?format=json`)
- [ ] 2.4 Correct in public: When the piece changes, publish a new record for the new text with `lineage` naming the old record, and file an erratum on the old one. (`POST /v1/errata`)
## 3. PromptReports products
- [ ] 3.1 CommitOps: If a post announces a shipped feature, link its record to the matching promise in CommitOps.
## 4. Verify against the live registry
- [ ] 4.1 Hash the published text: Re-hashing the live page body gives the record's asset digest.
- [ ] 4.2 Publish the record: `GET /r/{digest}` shows the record within seconds.
- [ ] 4.3 Embed the badge: The page links to /r/{digest} and carries the alternate link.
- [ ] 4.4 Correct in public: The old record's verdict becomes `check` with an `erratum` reason.
## 5. Done when
- [ ] 5.1 Every AI-assisted piece has a public record with model and editors.
- [ ] 5.2 Corrections appear on the record page and in the event feed.
OPENSPEC/CHANGES/INTEGRATE-AISOT-CONTENT-ATTRIBUTION/SPECS/CONTENT-ATTRIBUTION/SPEC.MD
# content-attribution Specification ## ADDED Requirements ### Requirement: A record per published piece The publishing pipeline SHALL post a signed provenance record for every AI-assisted piece before the piece goes live. #### Scenario: New post - **WHEN** a post is published - **THEN** a record with its digest, model and editors exists and the page links it #### Scenario: Refused record - **WHEN** the registry refuses the record - **THEN** the build fails and the post does not ship ### Requirement: Corrections are public A changed piece SHALL get a new record linked by `lineage`, and the old record SHALL get an erratum. #### Scenario: Edited post - **WHEN** the text of a live post changes - **THEN** a new record names the old one and the old one carries an erratum
$ curl -H 'Accept: application/json' https://aisourceoftruth.com/use-cases/attribute-published-content/openspec?lang=http&with=commitops # this page is a rendering of the API. Reproduce it as data.