yeti_types/plugins/trust.rs
1//! Plugin trust tier โ ADR-014 ยง4.
2//!
3//! Controls which host primitives a wasm component may import at link time.
4//!
5//! | Tier | Allowed primitives |
6//! |------------|--------------------------------------------------------------|
7//! | `Signed` | All: `transport.tcp`, `transport.tls`, `secrets`, plus safe |
8//! | `Unsigned` | Safe only: `http`, `persistence`, `pubsub`, `file`, `clock`, |
9//! | | `crypto`, `process`, `quota` |
10//!
11//! In `environment = "development"` all components are treated as `Signed`
12//! so unsigned plugins can be built and tested locally without a CI signing
13//! pipeline. In `environment = "production"` unsigned components attempting
14//! to use privileged primitives are rejected at link time.
15
16/// Trust classification assigned to a wasm component at load time.
17#[derive(
18 Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
19)]
20#[serde(rename_all = "snake_case")]
21pub enum PluginTrustTier {
22 /// Component is signed by a trusted key (cosign / sigil).
23 ///
24 /// May import raw transport (`transport.tcp`, `transport.tls`) and
25 /// the `secrets` interface. All safe-tier primitives also available.
26 Signed,
27
28 /// Component is unsigned (customer wasm or unsigned local plugin).
29 ///
30 /// May only import the safe high-level primitives: `http`, `persistence`,
31 /// `pubsub`, `file`, `clock`, `crypto`, `process`, `quota`.
32 /// Attempting to link against privileged primitives traps at instantiation.
33 #[default]
34 Unsigned,
35}
36
37impl PluginTrustTier {
38 /// Whether this tier may access raw TCP/TLS transport or the secrets interface.
39 #[must_use]
40 pub const fn is_privileged(&self) -> bool {
41 matches!(self, Self::Signed)
42 }
43
44 /// Determine the effective trust tier for a component given the environment
45 /// and whether a valid signature was found.
46 ///
47 /// In development mode the signing check is bypassed โ unsigned plugins load
48 /// and receive `Signed` privileges so local development doesn't require a
49 /// full cosign pipeline.
50 #[must_use]
51 pub fn resolve(is_signed: bool, environment: &str) -> Self {
52 if environment == "development" || is_signed {
53 Self::Signed
54 } else {
55 Self::Unsigned
56 }
57 }
58}
59
60impl std::fmt::Display for PluginTrustTier {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 Self::Signed => write!(f, "signed"),
64 Self::Unsigned => write!(f, "unsigned"),
65 }
66 }
67}