scv_server/config/
legacy.rs1use anyhow::{Result, bail};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Deprecated {
18 pub key: String,
20 pub value: String,
22 pub replacement: String,
24 pub mode: String,
26 pub origin: String,
28 pub pins_old_default: bool,
30}
31
32impl Deprecated {
33 pub fn warning(&self) -> String {
35 let (table, field) = self.replacement.rsplit_once('.').unwrap_or_default();
36 let advice = if self.pins_old_default {
37 "; it pins the old default, so delete it for auto"
38 } else if self.mode == "auto" {
39 ", which is now the default, so it can be deleted"
40 } else {
41 ""
42 };
43 format!(
44 "{}: {} = \"{}\" is deprecated and read as [{table}] {field} = \"{}\"; replace it \
45 with that line{advice}",
46 self.origin, self.key, self.value, self.mode
47 )
48 }
49
50 pub fn note(&self) -> String {
52 let pins = if self.pins_old_default {
53 "; pins the old default; delete it for auto"
54 } else {
55 ""
56 };
57 format!("set as deprecated {} = \"{}\"{pins}", self.key, self.value)
58 }
59}
60
61pub(super) fn migrate(layer: &mut toml::Value, origin: &str) -> Result<Vec<Deprecated>> {
64 let mut found = Vec::new();
65 let Some(root) = layer.as_table_mut() else {
66 return Ok(found);
67 };
68 let old_policy = root
69 .get_mut("tools")
70 .and_then(toml::Value::as_table_mut)
71 .and_then(|tools| tools.remove("approval_policy"));
72 if let Some(old) = old_policy {
73 let (value, mode, pins) = match old.as_str() {
74 Some("on-risk") => ("on-risk", "on-risk", true),
75 Some("always") => ("always", "always", false),
76 Some("never") => ("never", "read-only", false),
77 _ => bail!(
78 "{origin}: tools.approval_policy (deprecated) must be \"on-risk\", \"always\", or \
79 \"never\"; set [policy] mode instead"
80 ),
81 };
82 let policy = root
83 .entry("policy")
84 .or_insert_with(|| toml::Value::Table(toml::Table::new()));
85 let Some(policy) = policy.as_table_mut() else {
86 bail!("{origin}: [policy] must be a table");
87 };
88 if policy.contains_key("mode") {
89 bail!(
90 "{origin} sets both tools.approval_policy and policy.mode; keep only [policy] mode \
91 (tools.approval_policy is deprecated)"
92 );
93 }
94 policy.insert("mode".into(), mode.into());
95 found.push(Deprecated {
96 key: "tools.approval_policy".into(),
97 value: value.into(),
98 replacement: "policy.mode".into(),
99 mode: mode.into(),
100 origin: origin.into(),
101 pins_old_default: pins,
102 });
103 }
104 let agents = root
105 .get_mut("agents")
106 .and_then(toml::Value::as_table_mut)
107 .into_iter()
108 .flat_map(|agents| agents.iter_mut());
109 for (agent, table) in agents {
110 let Some(table) = table.as_table_mut() else {
111 continue;
112 };
113 let Some(old) = table.remove("permissions") else {
114 continue;
115 };
116 let (value, mode, pins) = match old.as_str() {
117 Some("full") => ("full", "auto", false),
118 Some("default") => ("default", "on-risk", true),
119 _ => bail!(
120 "{origin}: agents.{agent}.permissions (deprecated) must be \"default\" or \"full\"; \
121 set [agents.{agent}] mode instead"
122 ),
123 };
124 if table.contains_key("mode") {
125 bail!(
126 "{origin} sets both agents.{agent}.permissions and agents.{agent}.mode; keep only \
127 mode (permissions is deprecated)"
128 );
129 }
130 table.insert("mode".into(), mode.into());
131 found.push(Deprecated {
132 key: format!("agents.{agent}.permissions"),
133 value: value.into(),
134 replacement: format!("agents.{agent}.mode"),
135 mode: mode.into(),
136 origin: origin.into(),
137 pins_old_default: pins,
138 });
139 }
140 Ok(found)
141}