{
  "version": "sato-signing/v1",
  "issuer": "https://satohub.ai",
  "algorithm": {
    "alg": "EdDSA",
    "curve": "Ed25519",
    "references": [
      "RFC 8032 — Ed25519",
      "RFC 8037 §3.1 — EdDSA as a JWS algorithm; §2 — OKP JWK (kty, crv, x)",
      "RFC 7517 — JWK Set (/.well-known/jwks.json)",
      "RFC 7515 Appendix F / RFC 7797 — detached payload: the signature travels beside the content, not around it"
    ],
    "note": "The signature is the raw 64-byte Ed25519 signature, base64url, not a compact JWS. The header and the meta block carry the alg and kid that a protected header would otherwise hold."
  },
  "keys": {
    "jwks_url": "https://satohub.ai/.well-known/jwks.json",
    "active_kid": "b04bd38b",
    "kid_derivation": "first 8 hex characters of SHA-256 over the raw 32-byte Ed25519 public key"
  },
  "headers": {
    "signature": {
      "name": "Sato-Signature",
      "format": "kid=<kid>, alg=EdDSA, sig=<base64url>"
    },
    "signed_at": {
      "name": "Sato-Signed-At",
      "format": "ISO 8601 UTC timestamp; it is INSIDE the signed message, so a body cannot be replayed under another date"
    },
    "signature_input": {
      "name": "Sato-Signature-Input",
      "value": "covered=\"sato-signed-at,body\"; construct=\"utf8(sato-signed-at) || 0x0A || body-bytes\"; alg=EdDSA"
    }
  },
  "covered": {
    "header_signature": "utf8(Sato-Signed-At) || 0x0A || the exact response body bytes",
    "body_signature": "utf8(signed_at) || 0x0A || canonicalJson(body with meta.signature removed; the meta object itself is kept, and is present in every signed body); canonicalJson = JSON with object keys sorted by Unicode code point, array order preserved, no whitespace",
    "canonical_json": "Object keys sorted ascending by Unicode code point; array order preserved; no whitespace of any kind; undefined-valued keys omitted exactly as JSON.stringify omits them; numbers, strings, booleans and null serialised as JSON.stringify serialises them.",
    "why_two": "The header signature covers the bytes you received. The meta.signature block covers the canonical body with meta.signature removed, for consumers that never see headers — MCP structuredContent, a stored receipt, a payload pasted into a ticket."
  },
  "unsigned": {
    "meaning": "meta.signature: null and no Sato-Signature header means this deployment has no signing key configured.",
    "rule": "Unknown is published as unknown. A placeholder signature is never emitted."
  },
  "rotation": {
    "policy": "A retired public key stays in the JWKS for 90 days after it stops signing.",
    "why": "A verdict stored in a CI log or a receipt last month must still verify this month.",
    "on_compromise": "The key is replaced immediately and the compromised kid is removed from the JWKS rather than retired, and said so here."
  },
  "surfaces": [
    {
      "path": "/api/preflight",
      "what": "A Preflight verdict: what was checked, on what date, and what could not be checked."
    },
    {
      "path": "/api/route/swap",
      "what": "A swap route: the venue chosen, the fields it was chosen on, and the quote as read."
    },
    {
      "path": "/api/route/agent",
      "what": "An agent route: the passport chosen and the records it was chosen on."
    },
    {
      "path": "/api/route/launch",
      "what": "A launch route: the venue chosen and the fee parameters disclosed."
    },
    {
      "path": "/api/route/x402",
      "what": "An x402 route: the seller or facilitator chosen and what was observed about it."
    },
    {
      "path": "/api/route/lp",
      "what": "An LP route: the pool chosen and the readings behind it."
    },
    {
      "path": "/api/embed/stack.json",
      "what": "An embedded stack card: the listings shown, their scores, and the date the records carry. Signed because it renders on a domain we do not control."
    },
    {
      "path": "/api/verify/[slug]/proof",
      "what": "A Merkle proof that one listing's Sato Score was inside the root we committed to and published for that day."
    },
    {
      "path": "/api/movers.json",
      "what": "The week's Sato Score movement: which listings rose or fell, which crossed a tier, and whether the week is withheld because we revised the scoring method inside it."
    },
    {
      "path": "/api/satobot/plan",
      "what": "A build plan: the stack proposed and the listings it was drawn from. JSON only — the markdown rendering is unsigned."
    }
  ],
  "limits": [
    "A valid signature proves origin, integrity and the stamped time. It is not a safety, security or returns claim, and it does not make a verdict or a route more true.",
    "A quote is not a fill. A signed quote is a signed quote.",
    "Responses that are not JSON (for example ?format=markdown) are not signed; ask for JSON if you need a signature."
  ],
  "verify": "https://satohub.ai/trust-rules"
}