Skip to main content

CredentialStore

Trait CredentialStore 

Source
pub trait CredentialStore: Send + Sync {
    // Required methods
    fn get(
        &self,
        component: &str,
        key: &str,
    ) -> Result<Option<SecretRecord>, StoreError>;
    fn put(
        &self,
        component: &str,
        key: &str,
        rec: &SecretRecord,
    ) -> Result<(), StoreError>;
    fn erase(&self, component: &str, key: &str) -> Result<(), StoreError>;
    fn list(
        &self,
        component: Option<&str>,
    ) -> Result<Vec<SecretInfo>, StoreError>;
    fn components(&self) -> Result<Vec<String>, StoreError>;
    fn update(
        &self,
        component: &str,
        key: &str,
        mutate: &mut dyn FnMut(&mut SecretRecord),
    ) -> Result<Option<SecretRecord>, StoreError>;
}
Expand description

Keys are (component, key). component is the resolved reference the operator used, which is what makes the profile a per-component namespace.

Required Methods§

Source

fn get( &self, component: &str, key: &str, ) -> Result<Option<SecretRecord>, StoreError>

Source

fn put( &self, component: &str, key: &str, rec: &SecretRecord, ) -> Result<(), StoreError>

Source

fn erase(&self, component: &str, key: &str) -> Result<(), StoreError>

Source

fn list(&self, component: Option<&str>) -> Result<Vec<SecretInfo>, StoreError>

Metadata only, never values. None lists every component.

Source

fn components(&self) -> Result<Vec<String>, StoreError>

The components holding at least one credential.

list(None) flattens the profile away, which is the one thing a listing across components must not lose: profile keys are normalised (act-cli’s resolve::profile_key), so this is also how an operator sees which key a set actually landed under. Component names, like SecretInfo, cannot carry a value.

Source

fn update( &self, component: &str, key: &str, mutate: &mut dyn FnMut(&mut SecretRecord), ) -> Result<Option<SecretRecord>, StoreError>

Read, mutate and write one record atomically against other processes.

Ok(None) when the key does not exist; the mutation is not run.

This exists because get then put is not the same thing. Two act processes sharing the default store — two MCP servers under one client is the ordinary case — can interleave there, and the loser’s write wins with data read before the winner’s. For an OAuth refresh that is not a lost update but a dead credential: with refresh-token rotation the overwritten token is the only one the server still honours, and the user is sent back to act login with nothing to explain it.

An implementation MUST hold an exclusive advisory lock across the whole read-modify-write, and the caller MUST re-check inside the closure what it decided outside it: by the time the lock is held, the work may already have been done by whoever held it first.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§