lean_ctx/core/intelligence_runtime/
cli.rs1use std::collections::BTreeMap;
5use std::path::Path;
6
7use crate::core::config::{Config, IntelligenceRuntimeConfig};
8
9use super::{
10 InstallError, Result, RuntimeKind, install, manifest::MAX_ARCHIVE, read_regular, verify_kind,
11};
12
13pub fn run(args: &[String]) -> Result<String> {
14 let operation = args
15 .first()
16 .map(String::as_str)
17 .ok_or(InstallError::Usage)?;
18 if !matches!(
19 operation,
20 "verify"
21 | "install"
22 | "download"
23 | "sync"
24 | "rotate-root"
25 | "rollback"
26 | "health"
27 | "invoke"
28 | "activate"
29 | "deactivate"
30 | "status"
31 | "activate-configured"
32 | "sync-configured"
33 | "rollback-configured"
34 ) {
35 return Err(InstallError::Usage);
36 }
37 let mut flags = BTreeMap::new();
38 let mut arguments = args[1..].iter();
39 let mut staging = false;
40 let mut accepted = false;
41 while let Some(flag) = arguments.next() {
42 match flag.as_str() {
43 "--staging" if !staging => staging = true,
44 "--accept-proprietary" if !accepted => accepted = true,
45 "--archive"
46 | "--manifest"
47 | "--signature"
48 | "--manifest-sha256"
49 | "--trust-key-hex"
50 | "--root"
51 | "--expected-active"
52 | "--request"
53 | "--archive-url"
54 | "--manifest-url"
55 | "--signature-url"
56 | "--channel-url"
57 | "--transition"
58 | "--semantic-models"
59 | "--kind"
60 | "--channel-signature-url" => {
61 let value = arguments.next().ok_or(InstallError::Usage)?;
62 if value.starts_with("--") || flags.insert(flag.as_str(), value.as_str()).is_some()
63 {
64 return Err(InstallError::Usage);
65 }
66 }
67 _ => return Err(InstallError::Usage),
68 }
69 }
70 let companions = flags.get("--semantic-models").copied().map(Path::new);
73 if companions.is_some() && operation != "install" {
74 return Err(InstallError::Usage);
75 }
76 let kind = match flags.remove("--kind") {
79 None | Some("intelligence") => RuntimeKind::Intelligence,
80 Some("cockpit") => RuntimeKind::Cockpit,
81 Some(_) => return Err(InstallError::Usage),
82 };
83 if let Some(root) = flags.get("--root") {
84 let cockpit_store = Path::new(root)
85 .file_name()
86 .and_then(|name| name.to_str())
87 .is_some_and(|name| name.starts_with(RuntimeKind::Cockpit.store_name()));
88 if cockpit_store != (kind == RuntimeKind::Cockpit) {
89 return Err(InstallError::Usage);
90 }
91 }
92 if kind == RuntimeKind::Cockpit {
93 match operation {
94 "status" | "sync-configured" | "activate" | "deactivate" => {
95 return serde_json::to_string_pretty(&super::cockpit::run_cli(
96 operation, staging, accepted, &flags,
97 )?)
98 .map_err(|_| InstallError::State);
99 }
100 "verify" | "install" | "download" | "sync" | "rotate-root" if companions.is_none() => {}
101 _ => return Err(InstallError::Usage),
102 }
103 }
104 let configured_operation = matches!(
105 operation,
106 "status" | "sync-configured" | "rollback-configured" | "activate-configured" | "deactivate"
107 );
108 if !staging && !configured_operation {
109 return Err(InstallError::Usage);
110 }
111 if configured_operation && !matches!(operation, "status" | "deactivate") {
112 let observed = Config::try_load_global()
113 .map_err(|_| InstallError::Configuration)?
114 .intelligence_runtime;
115 let configured = if observed == IntelligenceRuntimeConfig::default() {
116 super::bootstrap::config()?
117 } else {
118 observed
119 };
120 if configured != IntelligenceRuntimeConfig::default()
121 && (configured.staging != staging || !super::bootstrap::permits_config(&configured))
122 {
123 return Err(InstallError::Usage);
124 }
125 }
126 if staging
127 && let Some(root) = flags.get("--root")
128 && super::bootstrap::production_key(Path::new(root))?.is_some()
129 {
130 return Err(InstallError::Usage);
131 }
132 if !matches!(operation, "verify" | "deactivate" | "status") && !accepted {
133 return Err(InstallError::Usage);
134 }
135 let get = |name| flags.get(name).copied().ok_or(InstallError::Usage);
136 if operation == "rotate-root" {
137 if flags.len() != 4 {
138 return Err(InstallError::Usage);
139 }
140 let proof = serde_json::from_slice(&read_regular(Path::new(get("--transition")?), 4096)?)
141 .map_err(|_| InstallError::Manifest)?;
142 let result = install::rotate_root(
143 Path::new(get("--root")?),
144 get("--expected-active")?,
145 &super::trust_key(get("--trust-key-hex")?)?,
146 proof,
147 )?;
148 return serde_json::to_string_pretty(&result).map_err(|_| InstallError::State);
149 }
150 if operation == "sync" {
151 if flags.len() != 5 {
152 return Err(InstallError::Usage);
153 }
154 let result = super::catalog::sync_kind(
155 kind,
156 [get("--channel-url")?, get("--channel-signature-url")?],
157 &super::trust_key(get("--trust-key-hex")?)?,
158 Path::new(get("--root")?),
159 get("--expected-active")?,
160 )?;
161 return serde_json::to_string_pretty(&result).map_err(|_| InstallError::State);
162 }
163 if matches!(
164 operation,
165 "status" | "activate-configured" | "sync-configured" | "rollback-configured"
166 ) {
167 if !flags.is_empty() {
168 return Err(InstallError::Usage);
169 }
170 let candidate = super::configuration::inspect_setup()?;
171 let result = if operation == "status" {
172 candidate.report()
173 } else if operation == "sync-configured" {
174 candidate.install_and_enable()?
175 } else if operation == "rollback-configured" {
176 candidate.rollback_and_enable()?
177 } else {
178 candidate.enable()?
179 };
180 return serde_json::to_string_pretty(&result).map_err(|_| InstallError::State);
181 }
182 if operation == "deactivate" {
183 if !flags.is_empty() {
184 return Err(InstallError::Usage);
185 }
186 let result = super::configuration::deactivate()?;
187 return serde_json::to_string_pretty(&result).map_err(|_| InstallError::State);
188 }
189 let selected = get("--manifest-sha256")?;
190 let encoded = get("--trust-key-hex")?;
191 let key = super::trust_key(encoded)?;
192 let result = if operation == "activate" {
193 if flags.len() != 4 || get("--expected-active")? != selected {
194 return Err(InstallError::Usage);
195 }
196 super::configuration::activate(Path::new(get("--root")?), selected, encoded)?
197 } else if operation == "invoke" {
198 if flags.len() != 5 || get("--expected-active")? != selected {
199 return Err(InstallError::Usage);
200 }
201 #[cfg(not(unix))]
202 return Err(InstallError::Exchange);
203 #[cfg(unix)]
204 {
205 let bytes = read_regular(
207 Path::new(get("--request")?),
208 lean_ctx_protocol::runtime_exchange::MAX_RUNTIME_EXCHANGE_BYTES,
209 )?;
210 let request = serde_json::from_slice(&bytes).map_err(|_| InstallError::Exchange)?;
211 let answer =
212 super::session::invoke(Path::new(get("--root")?), selected, &key, &request)?;
213 crate::core::execution_ledger::host::admit_foreign_document_narrative(
218 "runtime-invoke:",
219 &answer,
220 "engine-runtime-invoke",
221 )
222 .map_err(|_| InstallError::Exchange)?;
223 answer
224 }
225 } else if operation == "health" {
226 if flags.len() != 4 || get("--expected-active")? != selected {
227 return Err(InstallError::Usage);
228 }
229 super::health::check(Path::new(get("--root")?), selected, &key)?
230 } else if operation == "rollback" {
231 if flags.len() != 4 {
232 return Err(InstallError::Usage);
233 }
234 install::rollback(
235 Path::new(get("--root")?),
236 get("--expected-active")?,
237 selected,
238 &key,
239 )?
240 } else {
241 let expected = if operation == "verify" { 5 } else { 7 };
242 if flags.len() != expected + usize::from(companions.is_some()) {
243 return Err(InstallError::Usage);
244 }
245 let (archive, manifest, signature) = if operation == "download" {
246 get("--root")?;
247 get("--expected-active")?;
248 let fetched = super::channel::download(
249 [
250 get("--archive-url")?,
251 get("--manifest-url")?,
252 get("--signature-url")?,
253 ],
254 selected,
255 &key,
256 )?;
257 (fetched.archive, fetched.manifest, fetched.signature)
258 } else {
259 (
260 read_regular(Path::new(get("--archive")?), MAX_ARCHIVE)?,
261 read_regular(Path::new(get("--manifest")?), 65_536)?,
262 read_regular(Path::new(get("--signature")?), 64)?,
263 )
264 };
265 let package = verify_kind(kind, &archive, &manifest, &signature, selected, &key)?;
266 if operation == "verify" {
267 serde_json::json!({"status": "verified", "receipt": package.receipt,
268 "runtime_started": false, "release_approved": false})
269 } else {
270 let root = Path::new(get("--root")?);
271 let expected_active = get("--expected-active")?;
272 if let Some(companions) = companions {
273 install::install_with_companions(
274 root,
275 expected_active,
276 &package,
277 &archive,
278 &manifest,
279 &signature,
280 companions,
281 )?
282 } else {
283 install::install(
284 root,
285 expected_active,
286 &package,
287 &archive,
288 &manifest,
289 &signature,
290 )?
291 }
292 }
293 };
294 serde_json::to_string_pretty(&result).map_err(|_| InstallError::State)
295}