//! `vars::*` โ€” read-only access from scripts to the app's resolved //! configuration (Phase 3). Values are inherited down the group tree and //! env-filtered per the ยง3 resolution rule; the resolution happens in //! manager-core (`config_resolver`). Writes go through the admin API, not //! the SDK โ€” scripts only read. use std::collections::BTreeMap; use async_trait::async_trait; use serde_json::Value; use crate::SdkCallCx; #[derive(Debug, thiserror::Error)] pub enum VarsError { /// Caller principal lacked `AppVarsRead`. Only raised when /// `cx.principal.is_some()` (public-HTTP scripts skip the check โ€” /// script-as-gate). #[error("forbidden")] Forbidden, /// Postgres unavailable, malformed row, etc. #[error("vars backend error: {0}")] Backend(String), } #[async_trait] pub trait VarsService: Send + Sync { /// Resolve a single config key for the calling app's environment. /// `None` if no level defines it (or a tombstone suppresses it). async fn get(&self, cx: &SdkCallCx, key: &str) -> Result, VarsError>; /// The app's fully-resolved config map (every inherited + own key). async fn all(&self, cx: &SdkCallCx) -> Result, VarsError>; } /// All-noop fallback for engines that don't wire vars (tests). Every call /// surfaces an explicit error rather than silently returning empty. pub struct NoopVarsService; #[async_trait] impl VarsService for NoopVarsService { async fn get(&self, _cx: &SdkCallCx, _key: &str) -> Result, VarsError> { Err(VarsError::Backend("vars is not wired in".into())) } async fn all(&self, _cx: &SdkCallCx) -> Result, VarsError> { Err(VarsError::Backend("vars is not wired in".into())) } }