feat(backend): support des providers OpenCode cloud (#92)

Ajoute le catalogue statique de providers OpenCode (lot B3), le stockage
sécurisé des secrets (SecretStore + adapter infrastructure), et les
use cases SaveOpenCodeProviderProfile/DeleteProfile câblés en composition
root. Couvre le fix B1 et les tests de régression demandés par QA.

cargo build --workspace propre, cargo test --workspace -- --test-threads=1
intégralement vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 08:03:03 +02:00
parent bece7c92c5
commit 23a3c2788f
20 changed files with 1311 additions and 55 deletions

View File

@ -1730,6 +1730,67 @@ pub trait WindowStateStore: Send + Sync {
async fn load_window_state(&self) -> Result<crate::layout::WindowStateSnapshot, StoreError>;
}
/// Opaque lookup key for a value held in a [`SecretStore`] (ticket #92, lot B2).
/// Carries no semantics beyond "a stable reference" (e.g. a UUID string minted by
/// the caller) — the store never inspects or derives it. Attached to
/// [`crate::profile::OpenCodeProviderConfig`] and persisted as part of
/// `profiles.json`: safe, since it is only ever an opaque id, never the secret
/// value itself (see [`ProfileStore`]).
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct SecretRef(pub String);
impl SecretRef {
/// Wraps an opaque identifier string.
#[must_use]
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
/// Returns the inner identifier.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
/// Errors from the [`SecretStore`] port.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum SecretStoreError {
/// Underlying I/O error.
#[error("secret store io failed: {0}")]
Io(String),
/// Encryption/decryption failure (corrupt ciphertext, key mismatch, …).
#[error("secret store crypto failure: {0}")]
Crypto(String),
}
/// Port for at-rest storage of secret string values (ticket #92, lot B2, cadrage
/// Architect §B2) — keeps literal API keys OUT of `profiles.json`, which is plain
/// JSON with no encryption. Adapters implementing this port are the ONLY place
/// allowed to hold the encryption key; callers only ever handle plaintext values
/// and opaque [`SecretRef`]s.
#[async_trait]
pub trait SecretStore: Send + Sync {
/// Stores (creates or replaces) the secret value under `key`.
///
/// # Errors
/// [`SecretStoreError`] on I/O or encryption failure.
async fn put(&self, key: &SecretRef, value: &str) -> Result<(), SecretStoreError>;
/// Retrieves the secret value stored under `key`, or `None` if absent.
///
/// # Errors
/// [`SecretStoreError`] on I/O or decryption failure.
async fn get(&self, key: &SecretRef) -> Result<Option<String>, SecretStoreError>;
/// Deletes the secret stored under `key`. Deleting an absent key is a no-op
/// success (idempotent).
///
/// # Errors
/// [`SecretStoreError`] on I/O failure.
async fn delete(&self, key: &SecretRef) -> Result<(), SecretStoreError>;
}
/// CRUD for the configured [`AgentProfile`]s in the global IDE store
/// (`profiles.json`, ARCHITECTURE §9.2). Profiles are the *data* that drives the
/// single generic [`AgentRuntime`] adapter (Open/Closed).