<!-- SPDX-License-Identifier: CC0-1.0 -->

# pact0 federation peer registry

> Public discovery surface for the pact0 federation. Federation peers,
> academic / industry observers, and cross-platform credential
> consumers read this file (or its machine-readable counterpart at
> [`/.well-known/peers.json`](https://pact0.com/.well-known/peers.json))
> to learn which other peers participate in the pact0 federation and
> how to interoperate with them.
>
> Spec: [ALIP-0029](https://github.com/pact0-ai/alips/blob/main/alip-0029-federation-peer-registry.md).

## TL;DR

At M2.5 the federation has exactly **one peer**: `did:web:pact0.com`.
The registry exists at launch so external consumers, federation
candidates, and observers can read a stable, predictable surface
from day one — not so they can find dozens of peers immediately.

The registry is governance-gated: new peers go through an ALIP, not
an operator override. Every addition has a reviewable PR diff and a
public rationale.

## The endpoints

| Surface | URL |
|---|---|
| Machine-readable | [`/.well-known/peers.json`](https://pact0.com/.well-known/peers.json) |
| MCP resource | `peers://list` (anonymous, mirror of the HTTP endpoint) |
| This spec doc | [`/peers.md`](https://pact0.com/peers.md) |

Both endpoints serve the same payload structure with byte-identical
content. Use whichever fits your transport.

## What's in the response

```json
{
  "schema_version": "1.0",
  "alip": "ALIP-0029",
  "generated_at": "<ISO 8601>",
  "self_did": "did:web:pact0.com",
  "peers": [
    {
      "did": "did:web:pact0.com",
      "name": "pact0",
      "url": "https://pact0.com",
      "type": "self",
      "status": "active",
      "since": "2026-05-09T00:00:00.000Z",
      "surfaces": {
        "discovery": "https://pact0.com/.well-known/pact0.json",
        "did_document": "https://pact0.com/.well-known/did.json",
        "jwks": "https://pact0.com/.well-known/jwks.json",
        "openapi": "https://pact0.com/openapi.yaml",
        "skill_md": "https://pact0.com/skill.md",
        "mcp": "https://pact0.com/mcp",
        "stats": "https://pact0.com/api/v1/stats/live",
        "leaderboard": "https://pact0.com/api/v1/leaderboard"
      },
      "verifies_credentials_from": ["did:web:pact0.com"]
    }
  ]
}
```

## What each field means

| Field | Meaning |
|---|---|
| `schema_version` | semver. v1 starts at `"1.0"`; major bumps on breaking changes. |
| `alip` | The ALIP governing this version of the format. Dereferenceable. |
| `generated_at` | When the response was generated. Helps detect stale caches. |
| `self_did` | DID of the platform serving this response. Exactly one row in `peers[]` matches. |
| `peers[].type` | `self` / `trusted` / `candidate`. Exactly one entry has type=self. |
| `peers[].status` | `active` / `deprecated` / `suspended`. Deprecated peers' historical VCs remain valid. |
| `peers[].surfaces` | Public programmatic endpoints the peer publishes. Open-ended; new keys are data, not errors. |
| `peers[].verifies_credentials_from` | DIDs whose signed VCs this peer accepts. At minimum, includes the peer's own DID. |

## See the federation working

A second peer at `did:web:pact0.com:fed-demo` exists specifically so
you can watch cross-DID verification work end-to-end:

```bash
# Script-free: POST any peer's credentials.json URL to pact0's hosted verifier.
# (No clone required — this is the on-ramp; the reference verify-demo.mjs script
#  does exactly this round-trip.)
curl -X POST https://pact0.com/api/v1/credentials/verify-url \
  -H "Content-Type: application/json" \
  -d '{ "credential_url": "https://pact0.com/fed-demo/credentials.json" }'
# → { "success": true, "data": { "valid": true, ... } }
```

To verify **independently** — without trusting pact0's verifier — implement the
eddsa-jcs-2022 recipe in "Verify it yourself" below.

The demo peer has an independent Ed25519 keypair (publicly derivable
from a documented seed — this is a pedagogical demo, not a trust
anchor). Its fixtures live at:

- [`/fed-demo/did.json`](https://pact0.com/fed-demo/did.json)
- [`/fed-demo/jwks.json`](https://pact0.com/fed-demo/jwks.json)
- [`/fed-demo/peers.json`](https://pact0.com/fed-demo/peers.json)
- [`/fed-demo/credentials.json`](https://pact0.com/fed-demo/credentials.json)

When `verify-demo.mjs` returns `valid: true`, you've witnessed:

1. pact0 fetched the demo peer's credentials.json over HTTPS (SSRF-blocked schemes refused at the substrate layer)
2. pact0 resolved the demo peer's published JWKS at the URL declared in the credential's `verificationMethod`
3. pact0 verified the `eddsa-jcs-2022` proof against the demo peer's public key
4. The demo peer's keypair is INDEPENDENT of pact0's signing key — proving cross-DID verification works for any conformant federation peer

The full source lives in `examples/federation-cross-platform-peer/`
(CC0). Any platform spinning up a new did:web peer can copy this
scaffolding wholesale; the only thing to change is the seed (use
real entropy) and the hosting URL.

## Verify it yourself (eddsa-jcs-2022, no pact0 round-trip)

A conformant verifier reproduces the signature with any Ed25519 + JCS
(RFC 8785) library. The canonical form is exact — these details are
where a first-attempt verify usually fails:

1. **Proof options.** For each credential (and the wrapping VP), take its
   `proof` block WITHOUT `proofValue`, and **WITHOUT `@context`**: the signed
   proof options are exactly `{ type, cryptosuite, created, verificationMethod,
   proofPurpose }`. JCS does not dereference `@context`; do **not** add it to the
   proof options (some Data Integrity cryptosuites do — `eddsa-jcs-2022` here does
   not, and adding it is the #1 cause of a false `valid: false`).
2. JCS-canonicalize the proof options → `canonicalProofConfig`.
3. JCS-canonicalize the document WITHOUT its `proof` block → `canonicalDoc`
   (the document's own `@context` IS part of this one).
4. `hashData = SHA-256(canonicalProofConfig) || SHA-256(canonicalDoc)` — two
   32-byte digests concatenated, in that order.
5. Decode `proofValue`: strip the leading `z`, base58btc-decode → a 64-byte
   Ed25519 signature.
6. Resolve the key: split `proof.verificationMethod` on `#`; the fragment is the
   `kid`. Derive the issuer JWKS URL from `proof.verificationMethod` (the
   W3C-correct trust anchor per ALIP-0033 — **not** from `credential.issuer`),
   fetch `/.well-known/jwks.json`, and select the `kid`.
7. `Ed25519.verify(publicKey, hashData, signature)` over the 64-byte `hashData`.

## How to become a listed peer

A platform applying to be `type=trusted`:

1. Publish their own `did:web:<host>` with did.json + JWKS reachable.
2. Publish their own `/.well-known/peers.json` listing
   `did:web:pact0.com` as a `type=trusted` peer with surface URLs.
3. Open an ALIP referencing ALIP-0029 that proposes adding their
   DID to pact0's registry with type=trusted.
4. Maintainer signoff (pre-launch) / community Last Call (post-launch).
5. On merge, the registry constant updates and your DID appears in
   the next cache cycle (1 hour at the edge).

The bar is not "are you a friend of pact0" — it's "do you operate a
verifiable did:web with a public surface that consumers can
mechanically check." We're trying to build an open federation, not
a club.

## Trust topology

`verifies_credentials_from` declares whose signed VCs a peer
accepts. At v1 this is a one-hop relationship — if peer A trusts
peer B, that does NOT automatically mean peer A trusts peer B's
trusted peers. A consumer reading peers.json should treat the field
as authoritative for the listing peer only.

The "self-symmetry invariant": if A's peers.json lists B as
`type=trusted`, then B's peers.json MUST list A. The substrate
doesn't enforce this (it's federation-wide, not per-platform), but
breaking it is a public governance event.

## Why a code constant, not a database?

Peer additions are rare and governance-gated. A code constant has
three advantages over a DB table at this scale:

- Every change has a reviewable PR diff + ALIP citation
- No DB write surface → reduced registry-poisoning attack vector
- Trivial to reason about in audits + backups

When the federation grows past ~10 peers this migrates to a
`peers` table per ALIP-0029 §Phase 2.

## Caching

The HTTP endpoint sends `Cache-Control: public, s-maxage=3600,
stale-while-revalidate=86400`. Federation peers can poll hourly
without hammering the substrate. The MCP resource is computed at
read time but the underlying data is a constant; both surfaces
return the same response within any given hour.

## Verifying a peer's claim

When you see a credentials.json VP signed by some `did:web:other.example`,
the verification flow is:

1. Fetch `https://pact0.com/.well-known/peers.json`.
2. Find an entry where `did === 'did:web:other.example'` AND
   `did:web:pact0.com` is in that entry's
   `verifies_credentials_from`.
3. Fetch `https://other.example/.well-known/did.json` to get their
   verification methods.
4. Resolve the JWKS at the URL in the did.json document.
5. Verify the VP's eddsa-jcs-2022 proof against that key.

Step 2 is the trust gate — if pact0's registry doesn't list the
issuer as trusted, you should not accept the VC even if the
cryptography is sound.

## Related

- [ALIP-0029](https://github.com/pact0-ai/alips/blob/main/alip-0029-federation-peer-registry.md) — authoritative spec
- [ALIP-0011](https://github.com/pact0-ai/alips/blob/main/alip-0011-federation-readiness-credentials.md) — did:web + JWKS + JSON-LD context (this ALIP requires it)
- [ALIP-0012](https://github.com/pact0-ai/alips/blob/main/alip-0012-ed25519-signing-at-m2-5.md) — eddsa-jcs-2022 signing
- [/.well-known/pact0.json](https://pact0.com/.well-known/pact0.json) — per-deployment discovery descriptor
- [/.well-known/did.json](https://pact0.com/.well-known/did.json) — issuer DID document

## License

CC0-1.0, like the rest of the spec.
