use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use gradatum_embed::{Embedder, Noop as NoopEmbedder};
use async_trait::async_trait;
use gradatum_acl_auth::{ApiKeyStore, SqliteApiKeyStore};
use gradatum_acl_policy::AclEngine;
use gradatum_auth::jwt::JwtService;
use gradatum_auth::revocation::{InMemoryRevocationStore, RevocationStore};
use gradatum_core::audit::http::AuditSink;
use gradatum_core::error::GradatumError;
use gradatum_core::QueueStore;
use gradatum_vault::Registry;
use sqlx::SqlitePool;
use gradatum_queue::{JobId, JobInfo, LeasedJob, NewJob, Queue, QueueError};
use gradatum_core::index::Index;
use gradatum_index::SqliteIndex;
use crate::event_log_store::EventLogStore;
use crate::metrics::AppMetrics;
struct NoopAuditSink;
#[async_trait]
impl AuditSink for NoopAuditSink {
async fn record(
&self,
_event: gradatum_core::audit::http::HttpAuditEvent,
) -> Result<(), std::io::Error> {
Ok(())
}
}
fn placeholder_search() -> Arc<dyn Index> {
let idx = std::thread::spawn(|| {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("mini-runtime placeholder_search — invariant build")
.block_on(SqliteIndex::open_in_memory())
.expect("SqliteIndex::open_in_memory() placeholder — invariant init search")
})
.join()
.expect("thread join placeholder_search — invariant thread");
Arc::new(idx) as Arc<dyn Index>
}
#[derive(Debug, Clone)]
struct PlaceholderQueue;
#[async_trait]
impl Queue for PlaceholderQueue {
async fn get(&self, _id: JobId) -> Result<Option<JobInfo>, QueueError> {
Ok(None)
}
async fn enqueue(&self, _job: NewJob) -> Result<JobId, QueueError> {
Ok(0)
}
async fn lease(
&self,
_kinds: &[&str],
_duration: Duration,
) -> Result<Option<LeasedJob>, QueueError> {
Ok(None)
}
async fn complete(&self, _id: JobId) -> Result<(), QueueError> {
Ok(())
}
async fn fail(&self, _id: JobId, _err: &str) -> Result<(), QueueError> {
Ok(())
}
async fn extend_lease(&self, _id: JobId, _dur: Duration) -> Result<(), QueueError> {
Ok(())
}
async fn depth(&self) -> Result<u64, QueueError> {
Ok(0)
}
async fn oldest_age_secs(&self) -> Result<u64, QueueError> {
Ok(0)
}
}
struct NoopQueueStore;
#[async_trait]
impl QueueStore for NoopQueueStore {
async fn enqueue(
&self,
_job: gradatum_core::JobRecord,
) -> Result<ulid::Ulid, gradatum_core::QueueError> {
Err(gradatum_core::QueueError::Storage(
"NoopQueueStore : pas de job_store câblé — utiliser with_job_store".into(),
))
}
async fn dequeue(&self) -> Result<Option<gradatum_core::JobRecord>, gradatum_core::QueueError> {
Ok(None)
}
async fn get(
&self,
_id: ulid::Ulid,
) -> Result<Option<gradatum_core::JobRecord>, gradatum_core::QueueError> {
Ok(None)
}
async fn complete(
&self,
_id: ulid::Ulid,
_result: gradatum_core::JobResult,
) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn fail(
&self,
_id: ulid::Ulid,
_err: &str,
_attempt: u32,
) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn cancel(&self, _id: ulid::Ulid) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn fail_dlq(&self, _id: ulid::Ulid, _err: &str) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn find_awaiting(
&self,
_job_id: ulid::Ulid,
) -> Result<Vec<gradatum_core::JobRecord>, gradatum_core::QueueError> {
Ok(vec![])
}
async fn set_pending(&self, _id: ulid::Ulid) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn recover_stale_leases(
&self,
_ttl: std::time::Duration,
) -> Result<Vec<ulid::Ulid>, gradatum_core::QueueError> {
Ok(vec![])
}
async fn cancel_expired_deadlines(
&self,
_now: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<ulid::Ulid>, gradatum_core::QueueError> {
Ok(vec![])
}
async fn promote_retries(
&self,
_now: chrono::DateTime<chrono::Utc>,
) -> Result<Vec<ulid::Ulid>, gradatum_core::QueueError> {
Ok(vec![])
}
async fn schedule_retry(
&self,
_id: ulid::Ulid,
_at: chrono::DateTime<chrono::Utc>,
) -> Result<(), gradatum_core::QueueError> {
Ok(())
}
async fn list(
&self,
_filter: gradatum_core::JobFilter,
) -> Result<Vec<gradatum_core::JobRecord>, gradatum_core::QueueError> {
Ok(vec![])
}
fn subscribe(&self) -> tokio::sync::broadcast::Receiver<gradatum_core::QueueEvent> {
let (tx, rx) = tokio::sync::broadcast::channel(1);
drop(tx);
rx
}
}
struct NoopApiKeyStore;
#[async_trait]
impl ApiKeyStore for NoopApiKeyStore {
async fn create(
&self,
_owner: &str,
_scopes: Vec<String>,
_tenant_id: String,
_description: Option<String>,
) -> Result<gradatum_acl_auth::ApiKeyMaterial, gradatum_acl_auth::ApiKeyError> {
Err(gradatum_acl_auth::ApiKeyError::Crypto(
"NoopApiKeyStore : create() non supporté — câbler SqliteApiKeyStore via with_api_keys_path".into(),
))
}
async fn verify(
&self,
_secret: &str,
) -> Result<gradatum_acl_auth::ApiKey, gradatum_acl_auth::ApiKeyError> {
Err(gradatum_acl_auth::ApiKeyError::NotFound)
}
async fn list(
&self,
_include_revoked: bool,
) -> Result<Vec<gradatum_acl_auth::ApiKey>, gradatum_acl_auth::ApiKeyError> {
Ok(vec![])
}
async fn revoke(&self, _prefix: &str) -> Result<(), gradatum_acl_auth::ApiKeyError> {
Err(gradatum_acl_auth::ApiKeyError::NotFound)
}
async fn rotate(
&self,
_prefix: &str,
) -> Result<gradatum_acl_auth::ApiKeyMaterial, gradatum_acl_auth::ApiKeyError> {
Err(gradatum_acl_auth::ApiKeyError::NotFound)
}
}
#[derive(Debug, Clone)]
struct PlaceholderRegistry;
#[async_trait]
impl Registry for PlaceholderRegistry {
async fn tenant_count(&self) -> Result<u32, GradatumError> {
Ok(0)
}
async fn locus_count(&self) -> Result<u32, GradatumError> {
Ok(0)
}
async fn ensure_tenant(&self, _tenant_id: &str) -> Result<(), GradatumError> {
Ok(())
}
async fn read_note_by_id(
&self,
note_id: &str,
) -> Result<gradatum_core::note::Note, GradatumError> {
use gradatum_core::identity::NoteId;
use ulid::Ulid;
let ulid = Ulid::from_string(note_id)
.map_err(|e| GradatumError::Storage(format!("placeholder: ULID invalide: {e}")))?;
Err(GradatumError::NoteNotFound(NoteId(ulid)))
}
async fn history_versions(&self, _note_id: &str) -> Result<Vec<i64>, GradatumError> {
Ok(Vec::new())
}
async fn history_get(
&self,
note_id: &str,
_ts_ms: i64,
) -> Result<gradatum_core::note::Note, GradatumError> {
use gradatum_core::identity::NoteId;
use ulid::Ulid;
let ulid = Ulid::from_string(note_id)
.map_err(|e| GradatumError::Storage(format!("placeholder: ULID invalide: {e}")))?;
Err(GradatumError::NoteNotFound(NoteId(ulid)))
}
async fn history_restore(&self, _note_id: &str, _ts_ms: i64) -> Result<String, GradatumError> {
Err(GradatumError::Storage(
"history_restore: placeholder vault — injection requise".to_string(),
))
}
async fn history_diff(
&self,
_note_id: &str,
_a: &str,
_b: &str,
) -> Result<Vec<String>, GradatumError> {
Err(GradatumError::Storage(
"history_diff: placeholder vault — injection requise".to_string(),
))
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct AppState {
pub started_at: Instant,
pub started_at_systime: SystemTime,
pub version: &'static str,
pub build_sha: &'static str,
pub metrics: AppMetrics,
pub jwt: Arc<JwtService>,
pub acl: Arc<AclEngine>,
pub revocation: Arc<dyn RevocationStore>,
pub vault: Arc<dyn Registry>,
pub search: Arc<dyn Index>,
pub queue: Arc<dyn Queue>,
pub audit: Arc<dyn AuditSink>,
pub api_keys: Arc<dyn ApiKeyStore>,
pub embedder: Arc<dyn Embedder>,
pub reranker: Arc<dyn gradatum_search::Reranker>,
pub job_store: Arc<dyn QueueStore>,
pub jobs_pool: Option<sqlx::SqlitePool>,
pub event_log: Option<EventLogStore>,
}
impl AppState {
pub fn new() -> Self {
let jwt = JwtService::new_ephemeral();
Self::with_jwt(jwt)
}
pub fn with_jwt(jwt: JwtService) -> Self {
let acl = AclEngine::from_preset_str("")
.expect("le preset ACL vide est toujours valide — invariant statique");
Self {
started_at: Instant::now(),
started_at_systime: SystemTime::now(),
version: env!("CARGO_PKG_VERSION"),
build_sha: option_env!("BUILD_SHA").unwrap_or("unknown"),
metrics: AppMetrics::new(),
jwt: Arc::new(jwt),
acl: Arc::new(acl),
revocation: Arc::new(InMemoryRevocationStore::new()),
vault: Arc::new(PlaceholderRegistry),
search: placeholder_search(),
queue: Arc::new(PlaceholderQueue),
audit: Arc::new(NoopAuditSink),
api_keys: Arc::new(NoopApiKeyStore),
embedder: Arc::new(NoopEmbedder::new(384)),
reranker: Arc::new(gradatum_search::NoopReranker),
job_store: Arc::new(NoopQueueStore),
jobs_pool: None,
event_log: None,
}
}
#[allow(dead_code)] pub fn with_jwt_and_acl(jwt: JwtService, acl: AclEngine) -> Self {
Self {
started_at: Instant::now(),
started_at_systime: SystemTime::now(),
version: env!("CARGO_PKG_VERSION"),
build_sha: option_env!("BUILD_SHA").unwrap_or("unknown"),
metrics: AppMetrics::new(),
jwt: Arc::new(jwt),
acl: Arc::new(acl),
revocation: Arc::new(InMemoryRevocationStore::new()),
vault: Arc::new(PlaceholderRegistry),
search: placeholder_search(),
queue: Arc::new(PlaceholderQueue),
audit: Arc::new(NoopAuditSink),
api_keys: Arc::new(NoopApiKeyStore),
embedder: Arc::new(NoopEmbedder::new(384)),
reranker: Arc::new(gradatum_search::NoopReranker),
job_store: Arc::new(NoopQueueStore),
jobs_pool: None,
event_log: None,
}
}
#[allow(dead_code)] pub fn with_queue(mut self, queue: Arc<dyn Queue>) -> Self {
self.queue = queue;
self
}
pub async fn with_queue_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
use gradatum_queue::SqliteQueue;
let queue = SqliteQueue::new(path).await?;
self.queue = Arc::new(queue);
Ok(self)
}
pub async fn with_search_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
let index = SqliteIndex::open(path).await?;
self.search = Arc::new(index) as Arc<dyn Index>;
Ok(self)
}
#[allow(dead_code)]
pub fn with_vault_arc(mut self, vault: Arc<dyn Registry>) -> Self {
self.vault = vault;
self
}
#[allow(dead_code)]
pub fn with_audit(mut self, audit: Arc<dyn AuditSink>) -> Self {
self.audit = audit;
self
}
pub async fn with_vault_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
use gradatum_core::scope::VaultId;
let vault = if path.join(".gradatum").exists() {
gradatum_vault::Vault::open(path).await?
} else {
gradatum_vault::Vault::create(path, VaultId::new("main")).await?
};
self.vault = Arc::new(vault);
Ok(self)
}
pub async fn with_api_keys_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
let store = SqliteApiKeyStore::init(path)
.await
.map_err(|e| anyhow::anyhow!("SqliteApiKeyStore init failed: {e}"))?;
self.api_keys = Arc::new(store);
Ok(self)
}
pub async fn with_revocation_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
use gradatum_auth::revocation::SqliteRevocationStore;
let store = SqliteRevocationStore::new(path)
.await
.map_err(|e| anyhow::anyhow!("SqliteRevocationStore init failed: {e}"))?;
self.revocation = Arc::new(store);
Ok(self)
}
pub fn with_acl_preset_path(mut self, path: &std::path::Path) -> Self {
if path.as_os_str().is_empty() {
tracing::warn!("ACL preset absent ou illisible — fallback DENY-ALL (chemin vide)");
return self;
}
match std::fs::read_to_string(path) {
Ok(content) => match AclEngine::from_preset_str(&content) {
Ok(engine) => {
tracing::info!(path = %path.display(), "AclEngine chargé depuis preset");
self.acl = Arc::new(engine);
}
Err(e) => {
tracing::warn!(
path = %path.display(),
error = %e,
"ACL preset illisible (parse error) — fallback DENY-ALL"
);
}
},
Err(e) => {
tracing::warn!(
path = %path.display(),
error = %e,
"ACL preset absent ou illisible — fallback DENY-ALL"
);
}
}
self
}
#[allow(dead_code)]
pub async fn with_audit_dir(mut self, dir: &std::path::Path) -> anyhow::Result<Self> {
use crate::audit_jsonl::JsonlFileSink;
let sink = JsonlFileSink::new(dir.to_path_buf());
tokio::fs::create_dir_all(dir).await?;
self.audit = Arc::new(sink);
Ok(self)
}
pub fn with_embedder(mut self, embedder: Arc<dyn Embedder>) -> Self {
self.embedder = embedder;
self
}
#[allow(dead_code)]
pub fn with_reranker(mut self, reranker: Arc<dyn gradatum_search::Reranker>) -> Self {
self.reranker = reranker;
self
}
pub fn with_job_store(mut self, store: Arc<dyn QueueStore>, pool: SqlitePool) -> Self {
self.job_store = store;
self.jobs_pool = Some(pool);
self
}
pub async fn with_event_log_path(mut self, path: &std::path::Path) -> anyhow::Result<Self> {
use crate::event_log_store::EventLogStore;
let store = EventLogStore::open(path)
.await
.map_err(|e| anyhow::anyhow!("EventLogStore init failed: {e}"))?;
self.event_log = Some(store);
Ok(self)
}
#[cfg(test)]
pub fn with_event_log(mut self, store: EventLogStore) -> Self {
self.event_log = Some(store);
self
}
}
impl Default for AppState {
fn default() -> Self {
Self::new()
}
}