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:
@ -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).
|
||||
|
||||
Reference in New Issue
Block a user