Skip to content

crp.gateway

Auto-generated reference for the crp.gateway subpackage.

gateway

crp.gateway

CRP Gateway service - SPEC-016.

The Gateway is the hosted reverse proxy that turns a single base-URL change into full CRP governance on every AI call. This package contains the local implementation used for development and the self-hosted deployment path (Railway / Docker). The hosted managed service at gateway.crprotocol.io runs the same code.

Exports (lazy - heavy deps imported on first use): GatewayRequestLifecycle - 22-step request pipeline ProviderRouter - routes to OpenAI / Anthropic / local KeyVault - per-tenant encrypted key storage

GatewayRequestLifecycle

22-step CRP Gateway request lifecycle orchestrator.

Instantiate once per service process, reuse across requests.

Parameters:

Name Type Description Default
router Any | None

ProviderRouter instance. If None, a default local router is used.

None
session_store dict[str, Any] | None

Mutable dict used as a simple session persistence layer. In production, replace with Redis or another store.

None
rate_limit_store dict[str, Any] | None

Per-key rate-limit state dict (optional).

None
quota_store dict[str, Any] | None

Per-tenant quota state dict (optional).

None

aprocess(body, headers=None) async

Async 22-step lifecycle (use this from async servers).

process(body, headers=None)

Run the full 22-step lifecycle and return a response dict.

Parameters:

Name Type Description Default
body dict[str, Any]

Raw request body (JSON-decoded).

required
headers dict[str, str] | None

Raw request headers dict (lowercase keys preferred).

None

Returns:

Type Description
dict[str, Any]

Dict with keys 'body', 'headers', 'session_token'.

dict[str, Any]

If a safety halt fires, 'body' will be a HaltResponse dict and

dict[str, Any]

'headers' will include 'CRP-Safety-Halt: 1'.

Raises:

Type Description
_RateLimitExceeded

step 3 - caller should return HTTP 429.

_QuotaExhausted

step 4 - caller should return HTTP 402.

HaltResponse dataclass

HTTP 451 halt body (SPEC-033 §3.1, SPEC-016 §14).

to_body()

Build the response body for an HTTP 451 safety halt.

Returns:

Type Description
dict[str, Any]

Dict containing the public error details and CRP audit reference.

KeyVault

Per-tenant encrypted provider key storage.

For the self-hosted Gateway, keys are typically stored as environment variables (dev) or in a secrets manager (prod). For the hosted service, keys are stored encrypted in a per-tenant vault partition.

This class provides a consistent interface regardless of the backend.

Parameters:

Name Type Description Default
master_key bytes | None

Optional 32-byte master encryption key. If not provided, derives one from the CRP_VAULT_MASTER_KEY environment variable, or generates an ephemeral random key (dev mode - keys are in-memory only for the process lifetime).

None

get_provider_key(tenant_id, provider)

Retrieve and decrypt the API key for provider for tenant_id.

Resolution order
  1. Vault store (encrypted at rest) - for stored/provisioned keys.
  2. Environment variable override - CRP_KEY_<PROVIDER> or provider- specific env var (e.g. OPENAI_API_KEY).
  3. Empty string - caller may still proceed for local/no-auth providers.

Returns:

Type Description
str

Plaintext API key string (decrypted in memory, not cached).

store_key(tenant_id, provider, plaintext_key)

Encrypt and store an API key in the vault.

The plaintext key is encrypted immediately; the plaintext is not kept. Logs a safe hint (first 8 chars of the provider key).

Security note: in production, plaintext_key should be passed from a secrets manager or user input - never from a log or request body.

delete_key(tenant_id, provider)

Remove a stored key from the vault.

has_key(tenant_id, provider)

Return True if a key is stored in the vault (not env vars).

rotate_key(tenant_id, provider, new_plaintext_key)

Atomically replace an existing key with a new one.

export_encrypted_store()

Return a copy of the encrypted store (safe to log/serialise).

import_encrypted_store(store)

Restore a previously exported encrypted store.

ProviderRouter

Route an outbound request to the correct LLM provider.

The router is stateless per-request. It resolves the provider from the model name, fetches the API key from the KeyVault, applies failover on transient errors, and returns a normalised ProviderResponse.

Parameters:

Name Type Description Default
key_vault KeyVault | None

KeyVault instance. If None, a new ephemeral vault is created (useful for local development where keys come from env vars).

None
local_url str | None

Override for the local LM Studio / Ollama base URL.

None

resolve_provider(model, tenant_id)

Resolve which provider handles model for tenant_id.

Provider selection priority
  1. Explicit tenant override (future: from tenant config store).
  2. Model-name prefix table.
  3. Default: local.

Returns a ProviderConfig with the resolved base URL, API key, and any provider-specific extra headers.

dispatch(request, messages, session)

Dispatch a chat completion request to the resolved provider.

Attempts the primary provider. On transient error (5xx, timeout) tries once more with the same config (cold retry). Hard errors (auth failure, quota) propagate immediately.

Parameters:

Name Type Description Default
request Any

ChatRequest from api.py.

required
messages list[Any]

Augmented message list (post-CDR envelope).

required
session Any

GatewaySession (used for tenant_id, logging).

required

Returns:

Type Description
Any

ProviderResponse populated with content + token counts.

failover(primary_slug)

Return the failover provider slug for primary_slug (SPEC-016 §10).

handle_chat_completions(body, headers=None, lifecycle=None)

Convenience wrapper for /v1/chat/completions handler.

Suitable for use in FastAPI/Starlette/ASGI routes or called directly in tests.

Example::

result = handle_chat_completions(
    body={"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]},
    headers={"Authorization": "Bearer sk-test"},
)
assert result["status_code"] == 200
assert "CRP-Risk-Level" in result["headers"]

gateway.api

crp.gateway.api

CRP Gateway - 22-step OpenAI-compatible request lifecycle.

Implements the full governance pipeline defined in CRP-SPEC-016 and CRP-GATEWAY-BLUEPRINT.md. The 22 steps are grouped into four phases:

Inbound (1–5): TLS, auth, rate-limit, quota, parse Governance prep (6–9): session, safety policy, context mode, injection scan Execution (10–12): envelope build / STL, header strip, provider dispatch Governance analysis (13–18): DPE, safety enforcement, CSO, HMAC, state update Outbound (19–22): emit headers, audit stream, re-issue token, return

CRITICAL INVARIANT - Axiom 4: Step 11 strips ALL CRP- headers from the outbound provider request. NO CRP- header may reach the LLM provider. This is enforced by a hard allowlist filter tested in tests/test_gateway.py.

ChatMessage dataclass

A single message in a chat completion request (§2.1 of SPEC-016).

to_dict()

Return the message as an OpenAI-compatible dict.

Returns:

Type Description
dict[str, Any]

Dict with role, content, and optional name fields.

from_dict(d) classmethod

Create a new instance from a dictionary.

Parameters:

Name Type Description Default
d dict[str, Any]

The d value.

required

Returns:

Type Description
ChatMessage

ChatMessage.

ChatRequest dataclass

Parsed inbound /v1/chat/completions request.

from_body(body, headers) classmethod

Parse from a raw request dict + header map.

GatewaySession dataclass

Lightweight in-process session state for one request lifecycle.

record_audit(event_type, data)

Append a timestamped audit event to this session.

Parameters:

Name Type Description Default
event_type str

Category of the audit event.

required
data dict[str, Any]

Additional event fields to record.

required

ProviderResponse dataclass

Normalised response from any provider.

DPEReport dataclass

Lightweight DPE analysis result (SPEC-005).

should_halt property

Return whether this object should halt.

HaltResponse dataclass

HTTP 451 halt body (SPEC-033 §3.1, SPEC-016 §14).

to_body()

Build the response body for an HTTP 451 safety halt.

Returns:

Type Description
dict[str, Any]

Dict containing the public error details and CRP audit reference.

GatewayRequestLifecycle

22-step CRP Gateway request lifecycle orchestrator.

Instantiate once per service process, reuse across requests.

Parameters:

Name Type Description Default
router Any | None

ProviderRouter instance. If None, a default local router is used.

None
session_store dict[str, Any] | None

Mutable dict used as a simple session persistence layer. In production, replace with Redis or another store.

None
rate_limit_store dict[str, Any] | None

Per-key rate-limit state dict (optional).

None
quota_store dict[str, Any] | None

Per-tenant quota state dict (optional).

None

aprocess(body, headers=None) async

Async 22-step lifecycle (use this from async servers).

process(body, headers=None)

Run the full 22-step lifecycle and return a response dict.

Parameters:

Name Type Description Default
body dict[str, Any]

Raw request body (JSON-decoded).

required
headers dict[str, str] | None

Raw request headers dict (lowercase keys preferred).

None

Returns:

Type Description
dict[str, Any]

Dict with keys 'body', 'headers', 'session_token'.

dict[str, Any]

If a safety halt fires, 'body' will be a HaltResponse dict and

dict[str, Any]

'headers' will include 'CRP-Safety-Halt: 1'.

Raises:

Type Description
_RateLimitExceeded

step 3 - caller should return HTTP 429.

_QuotaExhausted

step 4 - caller should return HTTP 402.

handle_chat_completions(body, headers=None, lifecycle=None)

Convenience wrapper for /v1/chat/completions handler.

Suitable for use in FastAPI/Starlette/ASGI routes or called directly in tests.

Example::

result = handle_chat_completions(
    body={"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]},
    headers={"Authorization": "Bearer sk-test"},
)
assert result["status_code"] == 200
assert "CRP-Risk-Level" in result["headers"]

gateway.key_vault

crp.gateway.key_vault

CRP Gateway - Per-tenant encrypted key vault.

Stores and retrieves provider API keys for Gateway tenants. Keys are never logged, never exposed in headers, and are held in encrypted form at rest.

Security invariants (SPEC-016 §6, SPEC-015 §9): - Keys are AES-256-GCM encrypted at rest using a per-vault master key. - Keys are only decrypted in memory immediately before the provider call. - The plaintext key is NOT stored; the master key is required to recover it. - Encrypted values never appear in logs (only 8-character safe-prefix hint). - In development / test mode (no cryptography package), keys are stored as plain environment variable overrides with a clear advisory logged.

KeyVault

Per-tenant encrypted provider key storage.

For the self-hosted Gateway, keys are typically stored as environment variables (dev) or in a secrets manager (prod). For the hosted service, keys are stored encrypted in a per-tenant vault partition.

This class provides a consistent interface regardless of the backend.

Parameters:

Name Type Description Default
master_key bytes | None

Optional 32-byte master encryption key. If not provided, derives one from the CRP_VAULT_MASTER_KEY environment variable, or generates an ephemeral random key (dev mode - keys are in-memory only for the process lifetime).

None

get_provider_key(tenant_id, provider)

Retrieve and decrypt the API key for provider for tenant_id.

Resolution order
  1. Vault store (encrypted at rest) - for stored/provisioned keys.
  2. Environment variable override - CRP_KEY_<PROVIDER> or provider- specific env var (e.g. OPENAI_API_KEY).
  3. Empty string - caller may still proceed for local/no-auth providers.

Returns:

Type Description
str

Plaintext API key string (decrypted in memory, not cached).

store_key(tenant_id, provider, plaintext_key)

Encrypt and store an API key in the vault.

The plaintext key is encrypted immediately; the plaintext is not kept. Logs a safe hint (first 8 chars of the provider key).

Security note: in production, plaintext_key should be passed from a secrets manager or user input - never from a log or request body.

delete_key(tenant_id, provider)

Remove a stored key from the vault.

has_key(tenant_id, provider)

Return True if a key is stored in the vault (not env vars).

rotate_key(tenant_id, provider, new_plaintext_key)

Atomically replace an existing key with a new one.

export_encrypted_store()

Return a copy of the encrypted store (safe to log/serialise).

import_encrypted_store(store)

Restore a previously exported encrypted store.

gateway.router

crp.gateway.router

CRP Gateway - Provider Router.

Routes outbound LLM requests to the correct provider based on the model name and tenant configuration. Implements failover between providers.

Supported providers: openai, anthropic, local (LM Studio / Ollama) Future: gemini, bedrock (SPEC-016 §10).

SPEC-016 §8.

ProviderConfig dataclass

Resolved configuration for a single provider call.

ProviderRouter

Route an outbound request to the correct LLM provider.

The router is stateless per-request. It resolves the provider from the model name, fetches the API key from the KeyVault, applies failover on transient errors, and returns a normalised ProviderResponse.

Parameters:

Name Type Description Default
key_vault KeyVault | None

KeyVault instance. If None, a new ephemeral vault is created (useful for local development where keys come from env vars).

None
local_url str | None

Override for the local LM Studio / Ollama base URL.

None

resolve_provider(model, tenant_id)

Resolve which provider handles model for tenant_id.

Provider selection priority
  1. Explicit tenant override (future: from tenant config store).
  2. Model-name prefix table.
  3. Default: local.

Returns a ProviderConfig with the resolved base URL, API key, and any provider-specific extra headers.

dispatch(request, messages, session)

Dispatch a chat completion request to the resolved provider.

Attempts the primary provider. On transient error (5xx, timeout) tries once more with the same config (cold retry). Hard errors (auth failure, quota) propagate immediately.

Parameters:

Name Type Description Default
request Any

ChatRequest from api.py.

required
messages list[Any]

Augmented message list (post-CDR envelope).

required
session Any

GatewaySession (used for tenant_id, logging).

required

Returns:

Type Description
Any

ProviderResponse populated with content + token counts.

failover(primary_slug)

Return the failover provider slug for primary_slug (SPEC-016 §10).