Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

214 changes: 214 additions & 0 deletions components/markdoc/WitnessAnchorLoop.tsx
Original file line number Diff line number Diff line change
@@ -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<SVGSVGElement> & { size?: number }

const svg = (children: ReactNode) =>
function Icon({ size = 18, className, ...props }: IconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
{...props}
>
{children}
</svg>
)
}

const ScrollIcon = svg(
<>
<path d="M19 17V5a2 2 0 0 0-2-2H4" />
<path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3" />
</>,
)
const StampIcon = svg(
<>
<path d="M5 22h14" />
<path d="M19.27 13.73A2.5 2.5 0 0 0 17.5 13h-11A2.5 2.5 0 0 0 4 15.5V17a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-1.5c0-.66-.26-1.3-.73-1.77Z" />
<path d="M14 13V8.5C14 7 15 7 15 5a3 3 0 0 0-6 0c0 2 1 2 1 3.5V13" />
</>,
)
const EyeIcon = svg(
<>
<path d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0" />
<circle cx="12" cy="12" r="3" />
</>,
)
const ShieldIcon = svg(
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />,
)

const SEAL = '#e8845c'
const MUTE = '#9a948c'

function Down({ label }: { label: string }) {
return (
<div className="flex justify-center">
<div className="flex flex-col items-center gap-1 py-0.5">
<div className="h-4 w-px" style={{ background: `linear-gradient(${SEAL}80, ${SEAL}22)` }} />
<span className="text-[10px]" style={{ color: `${SEAL}99` }}>
{label}
</span>
<div className="h-4 w-px" style={{ background: `linear-gradient(${SEAL}22, ${SEAL}80)` }} />
</div>
</div>
)
}

function Node({
icon,
title,
sub,
glow = false,
}: {
icon: ReactNode
title: string
sub?: string
glow?: boolean
}) {
return (
<div
className="rounded-lg border px-4 py-3"
style={{
borderColor: `${SEAL}33`,
background: `${SEAL}0d`,
boxShadow: glow ? `0 0 30px ${SEAL}14` : undefined,
}}
>
<div className="flex items-center gap-2" style={{ color: SEAL }}>
{icon}
<span className="font-mono text-xs font-medium">{title}</span>
</div>
{sub ? <p className="mt-1 text-xs" style={{ color: MUTE }}>{sub}</p> : null}
</div>
)
}

function Witness({ name, where }: { name: string; where: string }) {
return (
<div
className="rounded-lg border px-3 py-2.5 text-center"
style={{ borderColor: `${SEAL}22`, background: `${SEAL}08` }}
>
<span className="mx-auto block w-fit" style={{ color: SEAL }}>
<StampIcon size={15} />
</span>
<div className="mt-1 font-mono text-[11px]" style={{ color: SEAL }}>
{name}
</div>
<div className="mt-0.5 text-[10px]" style={{ color: MUTE }}>
{where}
</div>
<div className="mt-1 text-[10px]" style={{ color: `${SEAL}cc` }}>
checks growth · cosigns · logs
</div>
</div>
)
}

function Chip({ text, accent = SEAL }: { text: string; accent?: string }) {
return (
<span
className="rounded-full border px-2 py-0.5 text-[10px]"
style={{ borderColor: `${accent}33`, background: `${accent}0d`, color: `${accent}cc` }}
>
{text}
</span>
)
}

export function WitnessAnchorLoop() {
return (
<div className="not-prose my-8 overflow-hidden rounded-lg bg-[#15130f] shadow-[0_24px_60px_-12px_rgba(28,24,20,0.45)] ring-1 ring-black/20">
<div className="flex items-center justify-between border-b border-white/5 px-5 py-2.5">
<span className="font-mono text-[11px] tracking-wider" style={{ color: MUTE }}>
the anchor loop
</span>
<span className="font-mono text-[11px]" style={{ color: MUTE }}>
no per-call data leaves home
</span>
</div>
<div className="p-6">
<div className="space-y-3">
<Node
icon={<ScrollIcon size={16} />}
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."
/>
<Down label="one signed anchor ⟨seed, head, k, cum, τ⟩" />
<div className="grid grid-cols-3 gap-2">
<Witness name="witness A" where="operator 1 · US" />
<Witness name="witness B" where="operator 2 · EU" />
<Witness name="witness C" where="operator 3 · APAC" />
</div>
<div className="flex items-center justify-center">
<span className="text-center text-[10px]" style={{ color: `${SEAL}cc` }}>
each accepts only monotone growth — same index, different head ⇒ a publishable
duplicity proof
</span>
</div>
<Down label="t-of-N cosignatures + log inclusion proofs" />
<Node
icon={<ShieldIcon size={16} />}
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
/>
<Down label="read by anyone" />
<div className="grid grid-cols-2 gap-2">
<div
className="rounded-lg border px-3 py-2.5 text-center"
style={{ borderColor: `${SEAL}22`, background: `${SEAL}08` }}
>
<span className="mx-auto block w-fit" style={{ color: SEAL }}>
<EyeIcon size={15} />
</span>
<div className="mt-1 font-mono text-[11px]" style={{ color: SEAL }}>
watchers
</div>
<div className="mt-0.5 text-[10px]" style={{ color: MUTE }}>
scan for forks and silence — the market is one
</div>
</div>
<div
className="rounded-lg border px-3 py-2.5 text-center"
style={{ borderColor: `${SEAL}22`, background: `${SEAL}08` }}
>
<span className="mx-auto block w-fit" style={{ color: SEAL }}>
<ShieldIcon size={15} />
</span>
<div className="mt-1 font-mono text-[11px]" style={{ color: SEAL }}>
verifiers
</div>
<div className="mt-0.5 text-[10px]" style={{ color: MUTE }}>
offline, free — now with fresh · stale · unanchored
</div>
</div>
</div>
<div className="flex flex-wrap justify-center gap-2 pt-1">
<Chip text="verification stays offline" />
<Chip text="rollback ⇒ signed contradiction" />
<Chip text="two histories ⇒ duplicity proof" />
<Chip text="witnesses see no per-call rows" accent={MUTE} />
</div>
</div>
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion content/docs/mcp/concepts/receipts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
2 changes: 1 addition & 1 deletion content/docs/mcp/guides/publish-receipts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
9 changes: 9 additions & 0 deletions content/docs/witness-network/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
97 changes: 97 additions & 0 deletions content/docs/witness-network/users/onboard-as-a-seller.md
Original file line number Diff line number Diff line change
@@ -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=<member key>" \
--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 <agent-did> --root <root-did> \
--out ./public/activity.json \
--anchor-to https://network.auths.dev \
--witness "network.auths.dev=<member key>" \
--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. |
2 changes: 2 additions & 0 deletions lib/markdoc-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -20,6 +21,7 @@ const components = {
CodeTab,
VerdictChip,
HeadingAnchor,
WitnessAnchorLoop,
}

/** Renders a transformed Markdoc tree to React — no HTML strings, ever. */
Expand Down
Loading
Loading