Self-service processor-fact attestation — a constrained request can finally route
2026-08-12
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.