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 | None |
get_provider_key(tenant_id, provider) ¶
Retrieve and decrypt the API key for provider for tenant_id.
Resolution order
- Vault store (encrypted at rest) - for stored/provisioned keys.
- Environment variable override -
CRP_KEY_<PROVIDER>or provider- specific env var (e.g.OPENAI_API_KEY). - 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
- Explicit tenant override (future: from tenant config store).
- Model-name prefix table.
- 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 |
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 |
|
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 | None |
get_provider_key(tenant_id, provider) ¶
Retrieve and decrypt the API key for provider for tenant_id.
Resolution order
- Vault store (encrypted at rest) - for stored/provisioned keys.
- Environment variable override -
CRP_KEY_<PROVIDER>or provider- specific env var (e.g.OPENAI_API_KEY). - 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
- Explicit tenant override (future: from tenant config store).
- Model-name prefix table.
- 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).