Self-service processor-fact attestation — a constrained request can finally route

2026-08-12

data-protectionsecurityrouter

LOCKSTEP TRACEABILITY MATRIX --- api_endpoints: ["POST /v1/security/processor-facts", "DELETE /v1/security/processor-facts"] sdk_methods_updated: [ "security.setProcessorFacts()", "security.clearProcessorFacts()", "security.set_processor_facts()", "security.clear_processor_facts()", ] mcp_tools_updated: ["none"] ---

What We Built

POST /v1/security/processor-facts (permission security.write, API-key auth) — the missing self-service write path that lets a tenant declare its OWN processor retention/training/region terms and have them govern routing. A companion DELETE clears them; the existing GET reads them back and POST .../validate dry-runs them.

Until now the retention model was correct but structurally un-actionable over an API key. The only way a fact reached tenant settings was PATCH /auth/tenant-settings (Supabase-JWT / dashboard only) or the validate-only endpoint. So a data_protection: { data_policy: "zero" } or { no_training: true } request always 403'd — the endpoint's dataRetention fact was fail-closed unknown ("retention for \"anthropic\" is unattested") and nothing an API client could call would ever declare it. This endpoint closes that gap: a tenant persists the contractual facts it holds (a ZDR/BAA/DPA), and the next constrained request can route.

The facts are stored raw under settings.dataProtection.processorFacts — the exact shape processorFactsFromTenantSettings() re-parses on the request path — so a write here flows into the data-protection middleware's endpoint-eligibility gate with no other wiring. Reuses the same parseProcessorFacts() validator as the dry-run surface.

Why It Matters

This is the honest trust model: the operator declares its own legal facts; the platform never infers or auto-attests. But "the operator must declare" is only a defensible posture if the operator actually _can_ declare — over the API, headless, without a human in a dashboard. Before this, a compliance-constrained agent was permanently 403'd with no programmatic remedy. Now an agent can attest once and route thereafter, and every attestation is content-free (provider ids, enum retention values, regions, ISO dates — never a secret, never request content).

How It Works

Attest Anthropic no-training + zero-retention for your account:

curl -X POST https://api.brainstormrouter.com/v1/security/processor-facts \
  -H "Authorization: Bearer $BR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "facts": [{
      "processor": "anthropic",
      "service": "completions",
      "effectiveFrom": "2026-01-01T00:00:00.000Z",
      "retention": { "mode": "zero" },
      "customerDataTraining": "prohibited",
      "evidenceRef": "ZDR-2026-01"
    }]
  }'

Fail-closed on parse: if any entry would be silently dropped by the parser (missing processor/service/valid effectiveFrom), the whole write is refused with a 400 invalid_processor_facts before any store is touched — a malformed assertion that reads as attested must never be persisted. Validate first at POST /v1/security/processor-facts/validate.

After the write, a subsequent POST /v1/chat/completions carrying data_protection: { no_training: true } (or retention_max_days: 0) resolves the tenant requirement, then checkEndpointEligibility() in src/api/middleware/data-protection.ts calls selectFact()evaluateFacts() against the just-stored fact. Anthropic now satisfies the requirement and the request routes instead of 403ing; the enforced posture is echoed in the X-BR-Data-Protection-Digest and data-policy headers.

The Numbers

  • 2 new endpoints (POST + DELETE); GET/validate already existed.
  • 0 new MCP tools — kept at 116, consistent with the sibling read/validate

capabilities (also non-MCP), so no tool-count drift.

  • 0 new stores — reuses the tenant-settings ConfigStore that

PATCH /auth/tenant-settings writes to.

Competitive Edge

Portkey / OpenRouter / Lasso treat data-residency and no-training as static vendor-level flags or dashboard toggles. BrainstormRouter models them as per-account, time-bounded, evidence-referenced _facts_ that a headless agent can assert over the API and that a tighten-only resolver enforces per request — an "unattested" processor satisfies nothing, and absence of evidence is never evidence of compliance.

Lockstep Checklist

> _Verified before committing this log._

  • [x] API Routes: src/api/capabilities/security/security.ts — added

security.processorFacts.set (POST) + security.processorFacts.delete (DELETE); registered in src/api/capabilities/_registry.ts.

  • [x] TS SDK: packages/sdk-ts/src/resources/security.ts

setProcessorFacts() + clearProcessorFacts() (generated tree regenerated via gen:contract, git-ignored).

  • [x] Python SDK: packages/sdk-py/.../resources/security.py

set_processor_facts() / clear_processor_facts() (sync + async).

  • [x] MCP Schemas: N/A — endpoint is not exposed as an MCP tool (matches

sibling processor-facts capabilities); agents.json agent_endpoints updated to advertise the self-service attestation path.

  • [x] Tests: src/api/capabilities/security/processor-facts.test.ts — fail-

closed 400 on drop, store-guard fall-through, and permission/method wiring.