use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::SystemTime;
use async_trait::async_trait;
use super::catalog::{
CatalogContent, CatalogError, CatalogModelEntry, CatalogProvider, CatalogRefresh,
CatalogSnapshot, CatalogSource, JsonPointer, Modality, ModelCapability, ModelFacts, ModelId,
ModelLimits, ObservedPrice, ObservedRate, PriceRates, ProviderEndpoint, ProviderId,
ProviderOffering, RawPayload, SchemaVersion, SourceValidators, source_snapshot,
};
use super::secrets::{
KekRef, SecretDescriptor, SecretError, SecretMaterial, SecretResolver, SecretStore,
};
use super::{Capabilities, Capability};
use crate::desired_state::secrets::{LifecycleTransition, SecretLifecycle, SecretOwner, SecretRef};
use crate::desired_state::{SecretId, Uuid7Generator};
struct Entry {
owner: SecretOwner,
lifecycle: SecretLifecycle,
material: Option<(String, KekRef)>,
}
pub(crate) struct InMemorySecrets {
entries: Mutex<HashMap<SecretRef, Entry>>,
kek: Mutex<KekRef>,
ids: Uuid7Generator,
unavailable: AtomicBool,
}
impl InMemorySecrets {
pub(crate) fn new() -> Self {
Self {
entries: Mutex::new(HashMap::new()),
kek: Mutex::new(KekRef("AXOND_KEK".to_owned())),
ids: Uuid7Generator::new(),
unavailable: AtomicBool::new(false),
}
}
pub(crate) fn seed(
&self,
owner: SecretOwner,
reference: SecretRef,
material: &str,
lifecycle: SecretLifecycle,
) {
let kek = self.kek.lock().expect("not poisoned").clone();
self.entries.lock().expect("not poisoned").insert(
reference,
Entry {
owner,
lifecycle,
material: (lifecycle != SecretLifecycle::Tombstoned)
.then(|| (material.to_owned(), kek)),
},
);
}
pub(crate) fn set_unavailable(&self, unavailable: bool) {
self.unavailable.store(unavailable, Ordering::Relaxed);
}
pub(crate) fn break_kek(&self) {
*self.kek.lock().expect("not poisoned") = KekRef("AXOND_KEK_ROTATED".to_owned());
}
pub(crate) fn holds_material(&self, reference: &SecretRef) -> bool {
self.entries
.lock()
.expect("not poisoned")
.get(reference)
.is_some_and(|entry| entry.material.is_some())
}
fn outage(&self) -> Option<SecretError> {
self.unavailable
.load(Ordering::Relaxed)
.then(|| SecretError::Unavailable {
backend: "in-memory",
message: "fake secret store is unavailable".to_owned(),
})
}
fn describe_locked(
entries: &HashMap<SecretRef, Entry>,
owner: SecretOwner,
reference: &SecretRef,
) -> Result<SecretDescriptor, SecretError> {
let entry = entries
.get(reference)
.ok_or(SecretError::NotFound(*reference))?;
if entry.owner != owner {
return Err(SecretError::Ownership {
reference: *reference,
owner,
});
}
Ok(SecretDescriptor {
reference: *reference,
owner: entry.owner,
lifecycle: entry.lifecycle,
})
}
}
#[async_trait]
impl SecretResolver for InMemorySecrets {
fn name(&self) -> &'static str {
"in-memory"
}
fn capabilities(&self) -> Capabilities {
Capabilities::new(&[Capability::EnvelopeEncryption])
}
async fn resolve(
&self,
owner: SecretOwner,
reference: &SecretRef,
) -> Result<SecretMaterial, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
let entries = self.entries.lock().expect("not poisoned");
let descriptor = Self::describe_locked(&entries, owner, reference)?;
if !descriptor.permits_resolution() {
return Err(SecretError::Lifecycle {
reference: *reference,
state: descriptor.lifecycle,
});
}
let entry = entries.get(reference).expect("described above");
let (material, sealed_under) = entry.material.as_ref().ok_or(SecretError::Lifecycle {
reference: *reference,
state: descriptor.lifecycle,
})?;
let kek = self.kek.lock().expect("not poisoned").clone();
if *sealed_under != kek {
return Err(SecretError::Unwrap {
reference: *reference,
kek,
});
}
Ok(SecretMaterial::new(material.clone()))
}
async fn exists(&self, owner: SecretOwner, reference: &SecretRef) -> Result<bool, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
let entries = self.entries.lock().expect("not poisoned");
match Self::describe_locked(&entries, owner, reference) {
Ok(descriptor) => Ok(descriptor.lifecycle.permits_resolution()),
Err(SecretError::NotFound(_) | SecretError::Ownership { .. }) => Ok(false),
Err(error) => Err(error),
}
}
}
#[async_trait]
impl SecretStore for InMemorySecrets {
async fn stage(
&self,
owner: SecretOwner,
material: SecretMaterial,
) -> Result<SecretDescriptor, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
if material.is_empty() {
return Err(SecretError::Invalid("material is empty".to_owned()));
}
let reference = SecretRef::first(SecretId::new(self.ids.next()));
let kek = self.kek.lock().expect("not poisoned").clone();
self.entries.lock().expect("not poisoned").insert(
reference,
Entry {
owner,
lifecycle: SecretLifecycle::Staged,
material: Some((material.expose().to_owned(), kek)),
},
);
Ok(SecretDescriptor {
reference,
owner,
lifecycle: SecretLifecycle::Staged,
})
}
async fn rotate(
&self,
owner: SecretOwner,
reference: &SecretRef,
material: SecretMaterial,
) -> Result<SecretDescriptor, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
if material.is_empty() {
return Err(SecretError::Invalid("material is empty".to_owned()));
}
let mut entries = self.entries.lock().expect("not poisoned");
let current = Self::describe_locked(&entries, owner, reference)?;
if current.lifecycle.is_terminal() {
return Err(SecretError::Lifecycle {
reference: *reference,
state: current.lifecycle,
});
}
let rotated = reference.rotated();
if entries.contains_key(&rotated) {
return Err(SecretError::VersionExists { reference: rotated });
}
let kek = self.kek.lock().expect("not poisoned").clone();
entries.insert(
rotated,
Entry {
owner,
lifecycle: SecretLifecycle::Staged,
material: Some((material.expose().to_owned(), kek)),
},
);
Ok(SecretDescriptor {
reference: rotated,
owner,
lifecycle: SecretLifecycle::Staged,
})
}
async fn transition(
&self,
owner: SecretOwner,
reference: &SecretRef,
next: SecretLifecycle,
) -> Result<LifecycleTransition, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
let mut entries = self.entries.lock().expect("not poisoned");
let current = Self::describe_locked(&entries, owner, reference)?;
let transition =
current
.lifecycle
.transition_to(next)
.map_err(|source| SecretError::Transition {
reference: *reference,
source,
})?;
let entry = entries.get_mut(reference).expect("described above");
entry.lifecycle = transition.state();
if entry.lifecycle == SecretLifecycle::Tombstoned {
entry.material = None;
}
Ok(transition)
}
async fn describe(
&self,
owner: SecretOwner,
reference: &SecretRef,
) -> Result<SecretDescriptor, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
let entries = self.entries.lock().expect("not poisoned");
Self::describe_locked(&entries, owner, reference)
}
async fn versions(
&self,
owner: SecretOwner,
secret: SecretId,
) -> Result<Vec<SecretDescriptor>, SecretError> {
if let Some(error) = self.outage() {
return Err(error);
}
let entries = self.entries.lock().expect("not poisoned");
let mut held: Vec<&SecretRef> = entries
.keys()
.filter(|reference| reference.secret == secret)
.collect();
held.sort_unstable();
let mut descriptors = Vec::with_capacity(held.len());
for reference in held {
match Self::describe_locked(&entries, owner, reference) {
Ok(descriptor) => descriptors.push(descriptor),
Err(SecretError::Ownership { .. }) => return Ok(Vec::new()),
Err(error) => return Err(error),
}
}
Ok(descriptors)
}
}
pub(crate) struct InMemoryCatalog {
validators: SourceValidators,
content: CatalogContent,
transfers: AtomicUsize,
unavailable: AtomicBool,
}
impl InMemoryCatalog {
pub(crate) fn with_models(models: &[(&str, &str)], etag: &str) -> Self {
let providers: Vec<CatalogProvider> = models
.iter()
.map(|(provider, _)| CatalogProvider {
id: ProviderId::parse(provider).expect("a canonical fake provider id"),
display_name: Some((*provider).to_owned()),
doc_url: None,
endpoint: ProviderEndpoint::default(),
env_vars: Vec::new(),
pointer: JsonPointer::new("").child("providers").child(provider),
})
.collect();
let entries: Vec<CatalogModelEntry> = models
.iter()
.map(|(provider, model)| {
let id = ModelId::parse(model).expect("a canonical fake model id");
let facts = ModelFacts {
display_name: Some((*model).to_owned()),
capabilities: [ModelCapability::ToolCall].into_iter().collect(),
input_modalities: [Modality::Text].into_iter().collect(),
output_modalities: [Modality::Text].into_iter().collect(),
limits: ModelLimits {
context_tokens: Some(128_000),
output_tokens: Some(16_384),
..ModelLimits::default()
},
..ModelFacts::default()
};
let pointer = JsonPointer::new("")
.child("providers")
.child(provider)
.child("models")
.child(model);
CatalogModelEntry {
id: id.clone(),
neutral: Some(facts.clone()),
offerings: vec![ProviderOffering {
provider: ProviderId::parse(provider).expect("a canonical fake id"),
model: id,
published_model_id: (*model).to_owned(),
facts,
overrides: Vec::new(),
price: Some(ObservedPrice::new(PriceRates::new(
ObservedRate::from_nanos(2_500_000_000),
ObservedRate::from_nanos(10_000_000_000),
))),
endpoint: ProviderEndpoint::default(),
pointer,
}],
}
})
.collect();
Self {
validators: SourceValidators::etag(etag),
content: CatalogContent::new(providers, entries).expect("a consistent fake catalogue"),
transfers: AtomicUsize::new(0),
unavailable: AtomicBool::new(false),
}
}
pub(crate) fn set_unavailable(&self, unavailable: bool) {
self.unavailable.store(unavailable, Ordering::Relaxed);
}
pub(crate) fn transfers(&self) -> usize {
self.transfers.load(Ordering::Relaxed)
}
}
#[async_trait]
impl CatalogSource for InMemoryCatalog {
fn name(&self) -> &'static str {
"in-memory"
}
fn capabilities(&self) -> Capabilities {
Capabilities::new(&[Capability::IncrementalRefresh, Capability::PriceMetadata])
}
async fn refresh(
&self,
since: Option<&SourceValidators>,
) -> Result<CatalogRefresh, CatalogError> {
if self.unavailable.load(Ordering::Relaxed) {
return Err(CatalogError::unavailable(
"in-memory",
"fake catalogue source is unavailable".to_owned(),
));
}
if since == Some(&self.validators) {
return Ok(CatalogRefresh::Unchanged {
validators: self.validators.clone(),
});
}
self.transfers.fetch_add(1, Ordering::Relaxed);
let source = source_snapshot(
"memory://catalogue",
SchemaVersion::MODELS_DEV_CATALOG_V1,
b"{}",
&self.content,
self.validators.clone(),
SystemTime::UNIX_EPOCH,
);
Ok(CatalogRefresh::Updated {
snapshot: Box::new(CatalogSnapshot {
source,
content: self.content.clone(),
}),
payload: RawPayload::new(&b"{}"[..]),
})
}
}