diff --git a/openspec/changes/database-llm-provider-management/.openspec.yaml b/openspec/changes/archive/2026-07-14-database-llm-provider-management/.openspec.yaml similarity index 100% rename from openspec/changes/database-llm-provider-management/.openspec.yaml rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/.openspec.yaml diff --git a/openspec/changes/database-llm-provider-management/design.md b/openspec/changes/archive/2026-07-14-database-llm-provider-management/design.md similarity index 100% rename from openspec/changes/database-llm-provider-management/design.md rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/design.md diff --git a/openspec/changes/database-llm-provider-management/proposal.md b/openspec/changes/archive/2026-07-14-database-llm-provider-management/proposal.md similarity index 100% rename from openspec/changes/database-llm-provider-management/proposal.md rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/proposal.md diff --git a/openspec/changes/database-llm-provider-management/specs/cloud-planner-proxy/spec.md b/openspec/changes/archive/2026-07-14-database-llm-provider-management/specs/cloud-planner-proxy/spec.md similarity index 100% rename from openspec/changes/database-llm-provider-management/specs/cloud-planner-proxy/spec.md rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/specs/cloud-planner-proxy/spec.md diff --git a/openspec/changes/database-llm-provider-management/specs/llm-provider-management/spec.md b/openspec/changes/archive/2026-07-14-database-llm-provider-management/specs/llm-provider-management/spec.md similarity index 100% rename from openspec/changes/database-llm-provider-management/specs/llm-provider-management/spec.md rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/specs/llm-provider-management/spec.md diff --git a/openspec/changes/database-llm-provider-management/tasks.md b/openspec/changes/archive/2026-07-14-database-llm-provider-management/tasks.md similarity index 100% rename from openspec/changes/database-llm-provider-management/tasks.md rename to openspec/changes/archive/2026-07-14-database-llm-provider-management/tasks.md diff --git a/openspec/specs/cloud-planner-proxy/spec.md b/openspec/specs/cloud-planner-proxy/spec.md index 416710a..e0d225f 100644 --- a/openspec/specs/cloud-planner-proxy/spec.md +++ b/openspec/specs/cloud-planner-proxy/spec.md @@ -25,7 +25,7 @@ The Cloud Control Plane SHALL expose an internal endpoint that accepts an AI Pla LLM provider ### Requirement: Endpoint resolves exactly one tool-call decision using cloud-held provider configuration -The Cloud Control Plane SHALL use its own configured LLM provider, model, and credentials -- not any value supplied by the requesting Host Agent -- to resolve a planner-decision request to exactly one tool name and one arguments object, within the request's timeout. +The Cloud Control Plane SHALL use its own configured LLM provider, model, and credentials -- not any value supplied by the requesting Host Agent -- to resolve a planner-decision request to exactly one tool name and one arguments object, within the request's timeout. The endpoint SHALL resolve the active database-managed Provider profile for every request and use its provider, model, timeout, configured base URL, and encrypted cloud-held credential. It SHALL NOT read Cloud API planner Provider/model/timeout/API-key environment variables. #### Scenario: Provider returns a usable decision - **WHEN** the configured provider responds to a planner-decision request @@ -39,6 +39,11 @@ The Cloud Control Plane SHALL use its own configured LLM provider, model, and cr - **THEN** the endpoint returns a structured failure response rather than a fabricated decision, and does not crash the Cloud Control Plane process +#### Scenario: Database profile is the only planner configuration source +- **WHEN** the Cloud API process has legacy planner environment variables +- **THEN** subsequent planner-decision requests use only the active database + profile and do not read a legacy Provider credential for that decision + ### Requirement: Cloud-proxy transport removes the need for Host Agent-held provider credentials A Host Agent using the cloud-proxy transport for its AI Planner SHALL be able to execute AI-planned tasks without any locally configured LLM provider API key. diff --git a/openspec/specs/llm-provider-management/spec.md b/openspec/specs/llm-provider-management/spec.md new file mode 100644 index 0000000..4571087 --- /dev/null +++ b/openspec/specs/llm-provider-management/spec.md @@ -0,0 +1,121 @@ +# llm-provider-management Specification + +## Purpose +Define how the Cloud Control Plane stores, protects, and activates +administrator-managed LLM Provider profiles in a durable database, so that +cloud-transport planner decisions resolve provider, model, timeout, base URL, +and API key from a single active database profile instead of from environment +variables. + +## Requirements + +### Requirement: Cloud-wide LLM Provider profiles are durable and validated +The Cloud Control Plane SHALL persist administrator-managed LLM Provider +profiles with a unique name, provider type, model, timeout, enabled state, +revision, and timestamps. A profile's provider type SHALL be either +`anthropic` or `openai-compatible`; either profile MAY specify an absolute +HTTP(S) base URL and SHALL use its official provider endpoint when it does not. + +#### Scenario: Administrator creates an OpenAI-compatible profile +- **WHEN** an authorized administrator submits a unique profile name, + `openai-compatible` provider type, model, valid timeout, API key, and an + optional valid base URL +- **THEN** the Cloud Control Plane persists an enabled profile with a new + revision and returns its non-secret metadata + +#### Scenario: Administrator creates an Anthropic profile with a custom base URL +- **WHEN** an authorized administrator submits a unique profile name, + `anthropic` provider type, model, valid timeout, API key, and a valid + Anthropic-compatible base URL +- **THEN** the Cloud Control Plane persists the normalized URL with the profile + and returns its non-secret metadata + +#### Scenario: Invalid profile configuration is rejected +- **WHEN** an administrator submits an unsupported provider type, blank model, + non-positive timeout, duplicate name, or invalid base URL +- **THEN** the Cloud Control Plane rejects the write without creating or + changing a profile + +### Requirement: Provider API keys are encrypted and never disclosed +The Cloud Control Plane SHALL encrypt Provider API keys before persistence +using a deployment-held encryption key, and SHALL not expose plaintext keys in +read responses, validation errors, audit records, or application logs. + +#### Scenario: Provider profile is listed after creation +- **WHEN** an authorized administrator lists Provider profiles after creating + one with an API key +- **THEN** every response reports only key-presence and rotation metadata and + does not contain the submitted API key or its ciphertext + +#### Scenario: Encryption configuration is unavailable +- **WHEN** a database-managed profile is created, rotated, activated, or + resolved without a valid deployment encryption key +- **THEN** the operation fails with a controlled configuration error that does + not reveal an API key + +### Requirement: Administrators can manage and activate Provider profiles +The Cloud Control Plane SHALL expose session-CSRF-protected and scope-guarded +operations to list, create, update, rotate a key, enable, disable, activate, +and delete inactive LLM Provider profiles. Mutating operations SHALL require +the `llm-providers:admin` scope and record a non-secret audit event. + +#### Scenario: Non-administrator attempts to modify a profile +- **WHEN** a principal without `llm-providers:admin` invokes a Provider + mutation endpoint +- **THEN** the Cloud Control Plane rejects the request before decrypting or + modifying a Provider credential + +#### Scenario: Administrator switches the active profile +- **WHEN** an authorized administrator activates an enabled profile +- **THEN** the Cloud Control Plane atomically selects that profile as the one + Cloud-wide active profile and records the activation without retaining an + API key in the audit event + +#### Scenario: Administrator attempts to retire the active profile +- **WHEN** an administrator attempts to disable or delete the active profile + before activating a replacement +- **THEN** the Cloud Control Plane rejects the operation and preserves the + active profile selection + +### Requirement: Activation dynamically selects one Provider for cloud transport +The Cloud Control Plane SHALL resolve the active database-managed profile for +each Cloud planner decision and SHALL apply an activation to subsequent +requests without requiring a Cloud API restart or any Host Agent +reconfiguration. + +#### Scenario: A Host requests a decision after a model switch +- **WHEN** an administrator activates a different enabled profile and a + cloud-transport Host Agent submits its next planner-decision request +- **THEN** the Cloud Control Plane calls that profile's provider, model, + timeout, base URL, and API key while the Host Agent continues using the same + Cloud Control Plane endpoint + +#### Scenario: Active OpenAI-compatible profile is used +- **WHEN** the active profile is OpenAI-compatible and includes a base URL +- **THEN** the Cloud Control Plane makes the existing OpenAI Chat Completions + tool-calling request to that base URL using the profile's decrypted API key + and returns the resulting single tool-call decision + +#### Scenario: Active Anthropic profile is used with a custom base URL +- **WHEN** the active profile is Anthropic and includes a base URL +- **THEN** the Cloud Control Plane makes the existing Anthropic native + tool-calling request to that base URL using the profile's decrypted API key + and returns the resulting single tool-call decision + +### Requirement: Cloud planner Provider configuration is database-only +The Cloud Control Plane SHALL resolve Cloud planner Provider, model, timeout, +base URL, and API key from the active database profile and SHALL NOT read +planner Provider/model/timeout or Provider API-key environment variables. +When no usable active profile exists, it SHALL fail a planner request with a +structured unavailable response. + +#### Scenario: Active profile is unavailable +- **WHEN** the active database Provider profile is missing, disabled, or + cannot be decrypted +- **THEN** the planner decision fails without invoking a legacy + environment-configured Provider + +#### Scenario: Legacy environment variables are present +- **WHEN** the Cloud API process has legacy planner Provider or API-key + environment variables but an active database profile exists +- **THEN** the planner decision uses only the active database profile