1use wasmtime::{ArrayRef, Linker, Rooted};
13
14use crate::session::SessionData;
15
16pub(crate) type StringArg = Option<Rooted<ArrayRef>>;
21pub(crate) type StringArgTriple = (StringArg, StringArg, StringArg);
24
25pub mod account;
26pub mod catch_each;
27pub mod commodity;
28pub mod config;
29pub mod env_io;
30mod generated_specs;
31pub mod meta;
32pub mod raise;
33mod render;
34pub mod report;
35pub mod split;
36pub mod ssh_key;
37pub mod template;
38pub mod transaction;
39
40pub use generated_specs::all_compiler_specs;
41pub use render::{RENDER_NATIVE_ALLOWLIST, link_render, render_compiler_specs};
42
43pub fn link(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
48 meta::register(linker)?;
49 env_io::register(linker)?;
50 raise::register(linker)?;
51 catch_each::register(linker)?;
52 account::register(linker)?;
53 commodity::register(linker)?;
54 config::register(linker)?;
55 report::register(linker)?;
56 split::register(linker)?;
57 ssh_key::register(linker)?;
58 template::register(linker)?;
59 transaction::register(linker)?;
60 Ok(())
61}
62
63#[must_use]
69pub fn all_registered_commands() -> Vec<&'static str> {
70 [
71 account::REGISTERED_COMMANDS,
72 commodity::REGISTERED_COMMANDS,
73 config::REGISTERED_COMMANDS,
74 report::REGISTERED_COMMANDS,
75 split::REGISTERED_COMMANDS,
76 ssh_key::REGISTERED_COMMANDS,
77 transaction::REGISTERED_COMMANDS,
78 ]
79 .iter()
80 .flat_map(|d| d.iter().copied())
81 .collect()
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn registry_lists_every_planned_command() {
90 let names = all_registered_commands();
91 assert_eq!(names.len(), 39, "command count drifted: {names:?}");
92 }
93
94 #[test]
95 fn registry_uses_kebab_case_only() {
96 for name in all_registered_commands() {
97 assert!(
98 name.chars()
99 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
100 "non-kebab-case name: {name}"
101 );
102 assert!(!name.starts_with('-'), "leading dash: {name}");
103 assert!(!name.ends_with('-'), "trailing dash: {name}");
104 }
105 }
106
107 #[test]
108 fn registry_has_no_duplicates() {
109 let names = all_registered_commands();
110 let mut sorted = names.clone();
111 sorted.sort_unstable();
112 sorted.dedup();
113 assert_eq!(
114 names.len(),
115 sorted.len(),
116 "duplicate command names: {names:?}"
117 );
118 }
119
120 #[test]
121 fn registry_excludes_verify_user_password_by_design() {
122 let names = all_registered_commands();
123 assert!(
124 !names.contains(&"verify-user-password"),
125 "verify-user-password took an arbitrary email and password and \
126 returned whether they matched — an unthrottled credential oracle \
127 against every account, callable by any verified user over the eval \
128 channel. It had no consumer. Do not re-add it without binding it to \
129 the calling user."
130 );
131 }
132
133 #[test]
134 fn registry_excludes_add_ssh_key_by_design() {
135 let names = all_registered_commands();
136 assert!(
137 !names.contains(&"add-ssh-key"),
138 "add-ssh-key must not be exposed via the eval channel; pubkey upload uses the dedicated ssh-copy-id exec path"
139 );
140 }
141
142 #[test]
143 fn each_domain_has_at_least_one_command() {
144 assert!(!account::REGISTERED_COMMANDS.is_empty());
145 assert!(!commodity::REGISTERED_COMMANDS.is_empty());
146 assert!(!config::REGISTERED_COMMANDS.is_empty());
147 assert!(!report::REGISTERED_COMMANDS.is_empty());
148 assert!(!split::REGISTERED_COMMANDS.is_empty());
149 assert!(!ssh_key::REGISTERED_COMMANDS.is_empty());
150 assert!(!transaction::REGISTERED_COMMANDS.is_empty());
151 }
152
153 #[test]
154 fn link_succeeds_against_session_data_linker() {
155 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
156 .expect("engine");
157 let mut linker: wasmtime::Linker<crate::session::SessionData> =
158 wasmtime::Linker::new(&engine);
159 link(&mut linker).expect("link must succeed");
160 }
161
162 #[test]
163 fn each_empty_server_domain_register_succeeds_in_isolation() {
164 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
169 .expect("engine");
170 let _engine = engine;
175 }
176
177 #[test]
178 fn account_register_succeeds_against_session_data() {
179 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
180 .expect("engine");
181 let mut linker: wasmtime::Linker<crate::session::SessionData> =
182 wasmtime::Linker::new(&engine);
183 account::register(&mut linker).expect("account::register must succeed");
184 }
185
186 #[test]
187 fn commodity_register_succeeds_against_session_data() {
188 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
189 .expect("engine");
190 let mut linker: wasmtime::Linker<crate::session::SessionData> =
191 wasmtime::Linker::new(&engine);
192 commodity::register(&mut linker).expect("commodity::register must succeed");
193 }
194
195 #[test]
196 fn transaction_register_succeeds_against_session_data() {
197 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
198 .expect("engine");
199 let mut linker: wasmtime::Linker<crate::session::SessionData> =
200 wasmtime::Linker::new(&engine);
201 transaction::register(&mut linker).expect("transaction::register must succeed");
202 }
203
204 #[test]
205 fn report_register_succeeds_against_session_data() {
206 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
207 .expect("engine");
208 let mut linker: wasmtime::Linker<crate::session::SessionData> =
209 wasmtime::Linker::new(&engine);
210 report::register(&mut linker).expect("report::register must succeed");
211 }
212
213 #[test]
214 fn split_register_succeeds_against_session_data() {
215 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
216 .expect("engine");
217 let mut linker: wasmtime::Linker<crate::session::SessionData> =
218 wasmtime::Linker::new(&engine);
219 split::register(&mut linker).expect("split::register must succeed");
220 }
221
222 #[test]
223 fn ssh_key_register_succeeds_against_session_data() {
224 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
225 .expect("engine");
226 let mut linker: wasmtime::Linker<crate::session::SessionData> =
227 wasmtime::Linker::new(&engine);
228 ssh_key::register(&mut linker).expect("ssh_key::register must succeed");
229 }
230
231 #[test]
232 fn config_register_succeeds_against_session_data() {
233 let engine = crate::wasm::build_engine(crate::wasm::EngineOpts::baseline().with_fuel())
238 .expect("engine");
239 let mut linker: wasmtime::Linker<crate::session::SessionData> =
240 wasmtime::Linker::new(&engine);
241 config::register(&mut linker).expect("config::register must succeed");
242 }
243}