← Developers

05.3 — Public Oracle & Key Scopes

Spec matching api v1

The Invariant

Don’t trust us — verify the record. The event oracle is public, rate-limited, and leaks nothing: no tenant_id, no credentials, no payload — just the integrity verdict.

The Contract

GET /api/v1/public/verify/event/:id   — unauthenticated, rate-limited 30/min

Three independent checks, all must pass for proof: "attributable_integrity_verified":

  1. signature_valid — Ed25519 over the preserved raw_body vs the node’s registered pubkey;
  2. event_in_signed_batch — the event_id belongs to the signed envelope;
  3. stored_payload_matches — the stored cp_events row was not altered post-ingest.

Framing: this is attributable integrity evidence — what an auditor, insurer or counterparty needs for ISO-9001-style verification. It is not qualified-eIDAS legal timestamping; that is an explicitly declared out-of-scope extension.

Key scopes (key_scope)

One API key per tenant, rotated via POST /admin/tenants/:id/api-key — rotation kills the previous key instantly (also the live-revocation demo). Denied → 403 SCOPE_INSUFFICIENT from authTenant before the heartbeat is touched.

Scope Capability Use
full Everything (back-compat default) operator consoles, legacy
sync /api/v1/sync/* only edge nodes (issued at claim)
read-only GET/HEAD/OPTIONS only, every surface BI, auditors, dashboards
ingest-only POST /sync/ingest + /sync/edge-audit only pure telemetry producers

The Flow

auditor: GET /public/verify/event/<id>
  → cp_events.batch_id → cp_ingest_batches (raw_body + signature)
  → cp_edge_nodes.node_pubkey → verify Ed25519 over raw bytes
  → event present in signed events[]? → stored payload == signed payload?
  → proof: attributable_integrity_verified

Payload Example

// 200 — verified
{ "event_id": "…", "signed": true, "node_id": "…",
  "signature_valid": true, "event_in_signed_batch": true,
  "stored_payload_matches": true,
  "proof": "attributable_integrity_verified" }

// 200 — unknown or unsigned event
{ "event_id": "…", "found": false }   // or signed: false — never a false positive

Known Failures

Symptom Cause → fix
403 SCOPE_INSUFFICIENT Key scope doesn’t cover method+path → rotate with the right scope
found:false on a known event Event arrived via unsigned legacy path (no batch) or wrong id — check signed flag
Oracle 429 30/min public rate limit — wait Retry-After
EDGE_SIGNATURE_REQUIRED Node has a registered pubkey → send x-edge-signature on exact body bytes