diff --git a/bun.lock b/bun.lock index 8340181..bb069b5 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,7 @@ "react-dom": "^19.0.0", }, "devDependencies": { + "@auths-dev/sdk": "0.1.15", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/typography": "^0.5.19", "@types/github-slugger": "^2.0.0", @@ -35,6 +36,18 @@ "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@auths-dev/sdk": ["@auths-dev/sdk@0.1.15", "", { "optionalDependencies": { "@auths-dev/sdk-darwin-arm64": "0.1.15", "@auths-dev/sdk-linux-arm64-gnu": "0.1.15", "@auths-dev/sdk-linux-x64-gnu": "0.1.15", "@auths-dev/sdk-win32-arm64-msvc": "0.1.15", "@auths-dev/sdk-win32-x64-msvc": "0.1.15" } }, "sha512-aWIjP2IDdIw0Qdiin/THaBJGjfx7FVy71JAkMiG8tTdng6xtV7u7nzyNppdIe+2UxaUPMM90jf4CTI0mzVFLFA=="], + + "@auths-dev/sdk-darwin-arm64": ["@auths-dev/sdk-darwin-arm64@0.1.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N1hPXwZH2ZsIS7hPHZe4bOrNr3PBU/Ta6apQrV6Jso+bjqZqIb0NOwVH40XtwkUYzXcNpkQ1Rz6UKmH6ix7b0Q=="], + + "@auths-dev/sdk-linux-arm64-gnu": ["@auths-dev/sdk-linux-arm64-gnu@0.1.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-rTwtoSZqrYAkcx/QECyQqEJKQ9mcZEYWTQMsHAN63Bs+7XeZ9H939zOa/CSAo1N17fkhDBFLT5wtVAKrRaRVBw=="], + + "@auths-dev/sdk-linux-x64-gnu": ["@auths-dev/sdk-linux-x64-gnu@0.1.15", "", { "os": "linux", "cpu": "x64" }, "sha512-r92MxjM84yweV4CD1dorLcG+AKg18/OkQ3HbWf9M8S2h+iIga1e+0wGgSEoDgFypgoCVXvD0gEqBmEHoA1ZRzw=="], + + "@auths-dev/sdk-win32-arm64-msvc": ["@auths-dev/sdk-win32-arm64-msvc@0.1.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-pHr5KADJAsK28C5iLWNtNHLvEehfKBg/tUxoKtFPTySRlys0SZpCXspO7FwaTgagzlBwN3uZqwk7oc+f91MhQA=="], + + "@auths-dev/sdk-win32-x64-msvc": ["@auths-dev/sdk-win32-x64-msvc@0.1.15", "", { "os": "win32", "cpu": "x64" }, "sha512-23A8ihkJ8r36rqh64na0rZcGIqjdg4TFrhvcDYzhKr59HR+TlDxoH0Z+WlSzSfBkdQBx2xT4cJyTetVpIcpcRA=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], diff --git a/components/markdoc/WitnessAnchorLoop.tsx b/components/markdoc/WitnessAnchorLoop.tsx new file mode 100644 index 0000000..e8488ba --- /dev/null +++ b/components/markdoc/WitnessAnchorLoop.tsx @@ -0,0 +1,214 @@ +import type { SVGProps, ReactNode } from 'react' + +/** + * The anchor loop — one aggregate, a t-of-N quorum, and everyone who reads it. + * + * Moved from the auths.dev marketing site (its `/network` page was retired in + * favour of the verifying explorer). Rendered here as the witness-network + * overview diagram via the Markdoc `{% witness-anchor-loop /%}` tag. Static + * (no scroll animation) — a diagram in docs doesn't need motion. + */ + +type IconProps = SVGProps & { size?: number } + +const svg = (children: ReactNode) => + function Icon({ size = 18, className, ...props }: IconProps) { + return ( + + {children} + + ) + } + +const ScrollIcon = svg( + <> + + + , +) +const StampIcon = svg( + <> + + + + , +) +const EyeIcon = svg( + <> + + + , +) +const ShieldIcon = svg( + , +) + +const SEAL = '#e8845c' +const MUTE = '#9a948c' + +function Down({ label }: { label: string }) { + return ( +
+
+
+ + {label} + +
+
+
+ ) +} + +function Node({ + icon, + title, + sub, + glow = false, +}: { + icon: ReactNode + title: string + sub?: string + glow?: boolean +}) { + return ( +
+
+ {icon} + {title} +
+ {sub ?

{sub}

: null} +
+ ) +} + +function Witness({ name, where }: { name: string; where: string }) { + return ( +
+ + + +
+ {name} +
+
+ {where} +
+
+ checks growth · cosigns · logs +
+
+ ) +} + +function Chip({ text, accent = SEAL }: { text: string; accent?: string }) { + return ( + + {text} + + ) +} + +export function WitnessAnchorLoop() { + return ( +
+
+ + the anchor loop + + + no per-call data leaves home + +
+
+
+ } + title="The agent's private spend chain" + sub="Every settled call appends to a signed hash chain. What leaves: one aggregate — head, count, cumulative — committing to all of it, revealing none of it." + /> + +
+ + + +
+
+ + each accepts only monotone growth — same index, different head ⇒ a publishable + duplicity proof + +
+ + } + title="Finalized anchor" + sub="Rolling history back now requires contradicting a quorum's signatures in append-only logs — a rollback is no longer a deletion, it is evidence." + glow + /> + +
+
+ + + +
+ watchers +
+
+ scan for forks and silence — the market is one +
+
+
+ + + +
+ verifiers +
+
+ offline, free — now with fresh · stale · unanchored +
+
+
+
+ + + + +
+
+
+
+ ) +} diff --git a/content/docs/mcp/concepts/receipts.md b/content/docs/mcp/concepts/receipts.md index 3d94759..27809d9 100644 --- a/content/docs/mcp/concepts/receipts.md +++ b/content/docs/mcp/concepts/receipts.md @@ -59,7 +59,7 @@ and no others: | `proof-unauthentic` | the signature does not verify against the agent's key | | `consistent` | the clean audit: every proof verifies, the spend re-derives | | `tampered-proof` | a signed proof was altered — caught offline | -| `dropped-call` | a record was removed from the log — each receipt back-links to the prior one | +| `chain-break` | a record was removed from the log — each receipt back-links to the prior one | | `budget-mismatch` | the durable cross-rail counter disagrees with the log (e.g. a truncated tail) | The last three are why handing you the log is not an act of trust: an **edited** diff --git a/content/docs/mcp/guides/publish-receipts.md b/content/docs/mcp/guides/publish-receipts.md index 26800d1..4a0f049 100644 --- a/content/docs/mcp/guides/publish-receipts.md +++ b/content/docs/mcp/guides/publish-receipts.md @@ -118,7 +118,7 @@ auths-mcp verify-spend --log spend.jsonl --registry ./registry \ Anything but {% verdict code="consistent" /%} is a loud, specific verdict — an edited record is {% verdict code="tampered-proof" /%}, a removed one -{% verdict code="dropped-call" /%}, a truncated tail +{% verdict code="chain-break" /%}, a truncated tail {% verdict code="budget-mismatch" /%}. See [what the audit catches](/mcp/concepts/receipts#what-the-audit-catches). diff --git a/content/docs/witness-network/index.md b/content/docs/witness-network/index.md index 073f0f0..c764f66 100644 --- a/content/docs/witness-network/index.md +++ b/content/docs/witness-network/index.md @@ -21,6 +21,15 @@ Witnesses co-sign every step forward. Rolling history back then means contradicting signatures sitting in append-only logs — the rollback stops being a deletion and becomes evidence. +## How it works + +An agent emits one aggregate — head, count, cumulative — to a set of independent +witnesses. Each cosigns only monotone growth, so presenting the same index with a +different head yields a publishable duplicity proof instead of a signature. The +finalized anchor is then read offline by watchers and verifiers alike. + +{% witness-anchor-loop /%} + ## Start here {% card-group %} diff --git a/content/docs/witness-network/users/onboard-as-a-seller.md b/content/docs/witness-network/users/onboard-as-a-seller.md new file mode 100644 index 0000000..28e55f5 --- /dev/null +++ b/content/docs/witness-network/users/onboard-as-a-seller.md @@ -0,0 +1,97 @@ +--- +title: "Onboard as a seller" +description: "The whole path in one page: create an identity, publish your KEL to your witnesses, anchor your witness set in your KEL, then export and publish a witnessed attestation." +product: witness +section: "Users" +order: 5 +lastReviewed: "2026-07-21" +--- + +Five steps, in order. Each later step verifies against the earlier ones, so +skipping one shows up as a failed verification — never as a silently weaker +listing. + +**Before you start:** the URL of at least one witness you trust — the +first-party node is `https://network.auths.dev` — and the CLI installed. + +## 1. Create your identity + +```bash +auths init +``` + +This incepts your KEL locally. Your identifier is derived from the content of +that first event itself — nobody can register it for you, and no registry +account exists to create. + +## 2. Designate your witnesses and publish your KEL + +```bash +auths witness add --url https://network.auths.dev +auths witness publish +``` + +`witness add` resolves the node's member key from `/health` and pins it — +future receipts must verify under exactly that key. `witness publish` submits +every event of your KEL to each configured witness, which validates it +cryptographically, stores it under your prefix in the registry it serves, and +returns a signed receipt. Submission is idempotent: republishing after adding +a witness (or after a rotation while offline) is always safe. + +From this point, anyone can resolve you from your witnesses: + +```bash +git fetch https://network.auths.dev '+refs/auths/*:refs/auths/*' +``` + +There is no registration call anywhere in this step — the signatures on your +events are the authorization. A witness accepts an event because the math +checks, or refuses it because it doesn't. + +## 3. Anchor your witness set in your KEL + +```bash +auths witness-set declare \ + --member "network.auths.dev=" \ + --threshold 1 +``` + +This computes the content hash (SAID) of the set and anchors it in your KEL +with an interaction event. It is what stops a counterfeit "witness set" from +being presented alongside your name: a verifier resolves the set's SAID from +YOUR KEL, so a set you never anchored is rejected outright. + +## 4. Export a witnessed attestation + +```bash +auths-mcp export-attestation \ + --live-dir ./live \ + --agent --root \ + --out ./public/activity.json \ + --anchor-to https://network.auths.dev \ + --witness "network.auths.dev=" \ + --witness-threshold 1 +``` + +The export collects cosignatures to your threshold and embeds the finalized +anchor inside `activity.json`. It fails outright below threshold — there is no +silent unanchored fallback. See +[Anchor your attestation](/witness-network/users/anchor-your-attestation) +for every flag. + +## 5. Publish at your listing's attestation URL + +Host `activity.json` (and `audit.json` if you publish per-call receipts) at a +stable HTTPS URL, and set that URL as your listing's `attestationUrl`. Buyers +and the market verify everything offline from those files plus your witnesses: +your keys from your KEL, your witness set from your KEL, the anchor from the +cosignatures, freshness from the latest anchor your witnesses serve. + +## What each step protects + +| Step | Without it | +| --- | --- | +| Publish your KEL | Nobody can resolve your current keys — every verification of you fails closed. | +| Anchor the witness set | A presented set is only self-consistent; an attacker could present a different "set" for you to different verifiers. | +| Anchored export | Your history's growth is unwitnessed — rollback of the recent past stays deniable. | +| Stable attestation URL | Buyers have nothing to verify against; the listing renders unverified. | diff --git a/lib/markdoc-components.tsx b/lib/markdoc-components.tsx index da124fe..c7a3633 100644 --- a/lib/markdoc-components.tsx +++ b/lib/markdoc-components.tsx @@ -7,6 +7,7 @@ import { Steps, Step } from '@/components/markdoc/Steps' import { CodeTabs, CodeTab } from '@/components/markdoc/CodeTabs' import { VerdictChip } from '@/components/markdoc/VerdictChip' import { HeadingAnchor } from '@/components/markdoc/HeadingAnchor' +import { WitnessAnchorLoop } from '@/components/markdoc/WitnessAnchorLoop' /** The single map from Markdoc tag names to React components. */ const components = { @@ -20,6 +21,7 @@ const components = { CodeTab, VerdictChip, HeadingAnchor, + WitnessAnchorLoop, } /** Renders a transformed Markdoc tree to React — no HTML strings, ever. */ diff --git a/markdoc/schema.ts b/markdoc/schema.ts index 281fb18..aca5f09 100644 --- a/markdoc/schema.ts +++ b/markdoc/schema.ts @@ -125,6 +125,10 @@ export function buildConfig(): Config { code: { type: String, required: true }, }, }, + 'witness-anchor-loop': { + render: 'WitnessAnchorLoop', + selfClosing: true, + }, }, } } diff --git a/package.json b/package.json index b1816c8..10658c4 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { + "@auths-dev/sdk": "0.1.15", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/typography": "^0.5.19", "@types/github-slugger": "^2.0.0", diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 664e6cf..2b8d410 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -13,29 +13,30 @@ import fs from 'node:fs' import path from 'node:path' +import { createRequire } from 'node:module' + +const require = createRequire(import.meta.url) const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..') const CONTENT = path.join(ROOT, 'content/docs') const checkExternal = !process.argv.includes('--no-external') -/** The exact verdict strings the gateway source defines — nothing else may appear. */ -const VERDICTS = new Set([ - 'allowed', - 'outside-agent-scope', - 'usage-cap-exceeded', - 'metered-amount-required', - 'usage-counter-rolled-back', - 'agent-expired', - 'revoked', - 'stale', - 'budget-required', - 'proof-unauthentic', - 'consistent', - 'tampered-proof', - 'cost-mismatch', - 'budget-mismatch', - 'dropped-call', -]) +/** + * The exact verdict strings the source defines — nothing else may appear. + * Loaded from the SDK's shipped verdict manifest (contracts/v1, generated from + * the gateway source), every family flattened into one set. The package's + * `exports` map does not expose conformance/*, so resolve the file from the + * package root rather than subpath-requiring it. + */ +const manifestPath = path.join( + path.dirname(require.resolve('@auths-dev/sdk/package.json')), + 'conformance/verdicts.json' +) +const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) +if (manifest.version !== 'contracts/v1') { + throw new Error(`unexpected verdict manifest version: ${manifest.version}`) +} +const VERDICTS = new Set(Object.values(manifest.verdicts).flat()) const BANNED = [ { re: /blockchain|decentraliz|self-sovereign/i, why: 'category vocabulary' },