Skip to main content

mlua_swarm/enhance/
setting.rs

1//! `EnhanceSetting` — the internal model that configures an
2//! `EnhanceApplication`.
3//!
4//! The internal storage form is a **BlueprintId ref**: the store does not
5//! hold the Blueprint body itself; that is resolved through
6//! `BlueprintStore`. HTTP `POST`/`PUT` input goes through
7//! [`EnhanceSettingInput`] and receives Blueprint data inline; the server
8//! orchestrates a `BPStore.write_new` and converts to a Ref before
9//! persisting.
10//!
11//! Runtime parameters (`ttl_secs`, `meta`) live on `EnhanceSetting`. The
12//! `EnhanceApplication` fetches the setting on every tick and picks up
13//! changes, so setting edits act as a hot reload.
14
15use crate::application::VersionSelector;
16use crate::blueprint::store::BlueprintId;
17use crate::blueprint::Blueprint;
18use serde::{Deserialize, Serialize};
19
20/// Internal storage form — the view held by the store and by
21/// `EnhanceApplication`. A `BlueprintId` ref plus runtime parameters.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct EnhanceSetting {
24    /// Setting id — the server's single default setting uses `"default"`.
25    pub id: String,
26    /// The Blueprint this setting resolves to, via `BlueprintStore`.
27    pub blueprint_id: BlueprintId,
28    /// Operator-session lifetime (the TTL passed to `Engine::attach`).
29    pub ttl_secs: u64,
30    /// Which `BlueprintVersion` to take (`Latest` / `Fixed` /
31    /// `SemverReq`).
32    #[serde(default)]
33    pub version: VersionSelector,
34    /// Enhance-flow verifier axes: on/off. Injected into the init ctx as
35    /// `$.verifiers` and fanned out in parallel by the flow.ir `Fanout`.
36    /// An empty array skips verification — the committer commits
37    /// unconditionally. Default: the four axes `["des", "canonical",
38    /// "noop", "agent-ref"]`.
39    #[serde(default = "default_verifier_axes")]
40    pub verifier_axes: Vec<String>,
41    /// Extension metadata slot (currently empty).
42    #[serde(default)]
43    pub meta: EnhanceSettingMeta,
44}
45
46fn default_verifier_axes() -> Vec<String> {
47    vec![
48        "des".to_string(),
49        "canonical".to_string(),
50        "noop".to_string(),
51        "agent-ref".to_string(),
52    ]
53}
54
55/// HTTP `POST`/`PUT` input shape — the caller's view. Blueprint data is
56/// inline; the server does `BPStore.write_new` and converts it to a Ref
57/// before persisting.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct EnhanceSettingInput {
60    /// Setting id — the server's single default setting uses `"default"`.
61    pub id: String,
62    /// Blueprint data inline; the server persists it via `BPStore.write_new`
63    /// and converts it to a `blueprint_id` ref before storing.
64    pub blueprint: Blueprint,
65    /// Operator-session lifetime (the TTL passed to `Engine::attach`).
66    pub ttl_secs: u64,
67    /// Which `BlueprintVersion` to take (`Latest` / `Fixed` / `SemverReq`).
68    #[serde(default)]
69    pub version: VersionSelector,
70    /// Enhance-flow verifier axes: on/off. Defaults to the four canonical
71    /// axes when omitted.
72    #[serde(default = "default_verifier_axes")]
73    pub verifier_axes: Vec<String>,
74    /// Extension metadata slot (currently empty).
75    #[serde(default)]
76    pub meta: EnhanceSettingMeta,
77}
78
79impl EnhanceSettingInput {
80    /// Convert an inline-data input into the Ref form
81    /// (`EnhanceSetting`). The Blueprint's `id` becomes the
82    /// setting's `blueprint_id`.
83    pub fn into_ref(self) -> (Blueprint, EnhanceSetting) {
84        let blueprint_id = BlueprintId::new(self.blueprint.id.clone());
85        (
86            self.blueprint,
87            EnhanceSetting {
88                id: self.id,
89                blueprint_id,
90                ttl_secs: self.ttl_secs,
91                version: self.version,
92                verifier_axes: self.verifier_axes,
93                meta: self.meta,
94            },
95        )
96    }
97}
98
99/// Extension metadata attached to an `EnhanceSetting`. Placeholder —
100/// something will land here for certain, so the slot exists up front.
101#[derive(Debug, Clone, Default, Serialize, Deserialize)]
102pub struct EnhanceSettingMeta {}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::enhance::blueprint::default_blueprint;
108
109    #[test]
110    fn default_verifier_axes_has_4_canonical_axes() {
111        let axes = default_verifier_axes();
112        assert_eq!(axes, vec!["des", "canonical", "noop", "agent-ref"]);
113    }
114
115    #[test]
116    fn input_into_ref_splits_blueprint_and_setting() {
117        let bp = default_blueprint();
118        let bp_id = bp.id.clone();
119        let input = EnhanceSettingInput {
120            id: "s1".into(),
121            blueprint: bp,
122            ttl_secs: 60,
123            version: VersionSelector::default(),
124            verifier_axes: default_verifier_axes(),
125            meta: EnhanceSettingMeta::default(),
126        };
127        let (split_bp, setting) = input.into_ref();
128        assert_eq!(setting.id, "s1");
129        assert_eq!(setting.blueprint_id.as_str(), bp_id);
130        assert_eq!(setting.ttl_secs, 60);
131        assert_eq!(setting.verifier_axes.len(), 4);
132        assert_eq!(split_bp.id, bp_id);
133    }
134
135    #[test]
136    fn setting_serde_roundtrip_preserves_verifier_axes() {
137        let bp_id = BlueprintId::new("bp-xyz".to_string());
138        let s = EnhanceSetting {
139            id: "s2".into(),
140            blueprint_id: bp_id,
141            ttl_secs: 30,
142            version: VersionSelector::default(),
143            verifier_axes: vec!["des".into(), "noop".into()],
144            meta: EnhanceSettingMeta::default(),
145        };
146        let j = serde_json::to_value(&s).unwrap();
147        let s2: EnhanceSetting = serde_json::from_value(j).unwrap();
148        assert_eq!(s2.verifier_axes, vec!["des", "noop"]);
149        assert_eq!(s2.ttl_secs, 30);
150    }
151
152    #[test]
153    fn setting_deserialize_applies_default_verifier_axes_when_omitted() {
154        let json = serde_json::json!({
155            "id": "s3",
156            "blueprint_id": "bp-1",
157            "ttl_secs": 10,
158        });
159        let s: EnhanceSetting = serde_json::from_value(json).unwrap();
160        assert_eq!(s.verifier_axes, default_verifier_axes());
161    }
162}