Files
PiCloud/crates/shared/src/vars.rs
MechaCat02 343f6d3b4d 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>
2026-06-24 20:59:23 +02:00

49 lines
1.8 KiB
Rust

//! `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()))
}
}