feat(vars): VarsService + vars:: SDK + config capabilities
Scripts can now read group-inherited config via vars::get(key) / vars::all(). - shared: VarsService trait (read-only get/all) + NoopVarsService; added as the 14th field on the Services bundle. - manager-core: VarsServiceImpl resolves the calling app's config via config_resolver (derives app_id from cx.app_id; AppVarsRead-gated for authed principals, script-as-gate for anon). - executor-core: vars:: Rhai bridge (get/all), registered in the SDK. - authz: AppVarsRead/Write, GroupVarsRead/Write, GroupSecretsRead/Write capabilities (group caps resolve via the Phase-2 ancestor walk; the GroupSecretsRead human-read gate is group_admin-only, distinct from an app's runtime injection). - wired VarsServiceImpl into the picloud binary; updated the executor-core test Services::new call sites. 386 manager-core lib tests green; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ pub mod subscriber_token;
|
||||
pub mod trigger_event;
|
||||
pub mod users;
|
||||
pub mod validator;
|
||||
pub mod vars;
|
||||
pub mod version;
|
||||
|
||||
/// serde `default` for `enabled`-style boolean fields that should default to
|
||||
@@ -101,4 +102,5 @@ pub use users::{
|
||||
UsersService,
|
||||
};
|
||||
pub use validator::{ScriptValidator, ValidatedScript, ValidationError};
|
||||
pub use vars::{NoopVarsService, VarsError, VarsService};
|
||||
pub use version::{API_VERSION, PRODUCT_VERSION, SDK_VERSION, WIRE_VERSION};
|
||||
|
||||
@@ -24,7 +24,8 @@ use crate::{
|
||||
KvService, ModuleSource, NoopDeadLetterService, NoopDocsService, NoopEmailService,
|
||||
NoopEventEmitter, NoopFilesService, NoopHttpService, NoopInvokeService, NoopKvService,
|
||||
NoopModuleSource, NoopPubsubService, NoopQueueService, NoopSecretsService, NoopUsersService,
|
||||
PubsubService, QueueService, SecretsService, ServiceEventEmitter, UsersService,
|
||||
NoopVarsService, PubsubService, QueueService, SecretsService, ServiceEventEmitter,
|
||||
UsersService, VarsService,
|
||||
};
|
||||
|
||||
/// SDK service bundle. See module docs for the lifecycle and the v1.1.x
|
||||
@@ -107,6 +108,12 @@ pub struct Services {
|
||||
/// and `invoke_async()` (fire-and-forget through the outbox).
|
||||
/// Cross-app invokes are rejected at the service entry point.
|
||||
pub invoke: Arc<dyn InvokeService>,
|
||||
|
||||
/// Group-inherited, env-scoped config (Phase 3). Scripts get
|
||||
/// read-only `vars::{get,all}`; values resolve down the group tree
|
||||
/// (§3). Backed by `config_resolver` over Postgres in the picloud
|
||||
/// binary; `NoopVarsService` in tests that don't read config.
|
||||
pub vars: Arc<dyn VarsService>,
|
||||
}
|
||||
|
||||
impl Services {
|
||||
@@ -129,6 +136,7 @@ impl Services {
|
||||
users: Arc<dyn UsersService>,
|
||||
queue: Arc<dyn QueueService>,
|
||||
invoke: Arc<dyn InvokeService>,
|
||||
vars: Arc<dyn VarsService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
kv,
|
||||
@@ -144,6 +152,7 @@ impl Services {
|
||||
users,
|
||||
queue,
|
||||
invoke,
|
||||
vars,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +177,7 @@ impl Services {
|
||||
Arc::new(NoopUsersService),
|
||||
Arc::new(NoopQueueService),
|
||||
Arc::new(NoopInvokeService),
|
||||
Arc::new(NoopVarsService),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
48
crates/shared/src/vars.rs
Normal file
48
crates/shared/src/vars.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! `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<Option<Value>, VarsError>;
|
||||
|
||||
/// The app's fully-resolved config map (every inherited + own key).
|
||||
async fn all(&self, cx: &SdkCallCx) -> Result<BTreeMap<String, Value>, 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<Option<Value>, VarsError> {
|
||||
Err(VarsError::Backend("vars is not wired in".into()))
|
||||
}
|
||||
async fn all(&self, _cx: &SdkCallCx) -> Result<BTreeMap<String, Value>, VarsError> {
|
||||
Err(VarsError::Backend("vars is not wired in".into()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user