Skip to content

Commit 0ec944e

Browse files
westgatewestgate
authored andcommitted
S190: Wire Standard L2 compliance — capabilities.list, health.liveness, identity.get
capabilities.list now returns {primal, version, methods, provided_capabilities} per CAPABILITY_WIRE_STANDARD.md v1.0. health.liveness adds "status": "alive" for biomeOS probes. identity.get adds domain and license fields. compute.capabilities split off to retain hardware metadata for compute clients. Made-with: Cursor
1 parent 3f6134b commit 0ec944e

3 files changed

Lines changed: 213 additions & 36 deletions

File tree

crates/integration-tests/tests/pure_rust_validation_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ fn test_only_acceptable_sys_crates() {
398398
crate_line.contains("drm-sys") || // DRM/KMS kernel ioctls (display crate) ✅
399399
crate_line.contains("pyo3-ffi") || // Python FFI (optional) ✅
400400
crate_line.contains("seccomp-sys") || // Security (optional) ✅
401-
crate_line.contains("renderdoc-sys"); // GPU debugging (optional) ✅
401+
crate_line.contains("renderdoc-sys"); // GPU debugging (optional) ✅
402402

403403
assert!(
404404
is_acceptable,

crates/server/src/pure_jsonrpc/handler/core.rs

Lines changed: 208 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use crate::rpc_types::HealthStatus;
1515
type JsonRpcResult = Result<serde_json::Value, JsonRpcError>;
1616

1717
/// Returns health status with uptime and error count.
18+
///
19+
/// Wire Standard L1: includes `"status": "alive"` for biomeOS liveness probes.
1820
#[expect(
1921
clippy::unused_async,
2022
reason = "handler signature requires async for uniform dispatch"
@@ -39,8 +41,13 @@ pub(crate) async fn health(
3941
error_count: error_count_val,
4042
resource_utilization: 0.0,
4143
};
42-
serde_json::to_value(status)
43-
.map_err(|e| JsonRpcError::internal_error(format!("Serialization error: {e}")))
44+
let mut value = serde_json::to_value(status)
45+
.map_err(|e| JsonRpcError::internal_error(format!("Serialization error: {e}")))?;
46+
// Wire Standard L1: biomeOS probes expect "status": "alive"
47+
if let Some(obj) = value.as_object_mut() {
48+
obj.insert("status".into(), serde_json::Value::String("alive".into()));
49+
}
50+
Ok(value)
4451
}
4552

4653
/// Returns version and protocol information.
@@ -60,30 +67,31 @@ pub(crate) async fn version_info(version: &str) -> JsonRpcResult {
6067
Ok(serde_json::json!(info))
6168
}
6269

63-
/// Returns discovered capabilities including semantic methods.
64-
#[expect(
65-
clippy::unused_async,
66-
reason = "handler signature requires async for uniform dispatch"
67-
)]
68-
pub(crate) async fn discover_capabilities(
69-
semantic_registry: &SemanticMethodRegistry,
70-
version: &str,
71-
) -> JsonRpcResult {
70+
/// Builds the sorted flat methods list from direct routes + semantic registry.
71+
fn all_callable_methods(semantic_registry: &SemanticMethodRegistry) -> Vec<&str> {
7272
let semantic_methods: Vec<&str> = semantic_registry.semantic_names().into_iter().collect();
7373

74-
let mut direct_methods = vec![
74+
let mut methods = vec![
75+
"capabilities.list",
7576
"identity.get",
7677
"health.liveness",
7778
"health.readiness",
79+
"health.check",
7880
"toadstool.health",
7981
"toadstool.version",
82+
"toadstool.submit_workload",
83+
"toadstool.query_status",
84+
"toadstool.cancel_workload",
85+
"toadstool.list_workloads",
8086
"toadstool.query_capabilities",
8187
"toadstool.resources.estimate",
8288
"toadstool.resources.validate_availability",
8389
"toadstool.resources.suggest_optimizations",
8490
"resources.estimate",
8591
"resources.validate_availability",
8692
"resources.suggest_optimizations",
93+
"ai.local_inference",
94+
"ai.local_execute",
8795
"compute.health",
8896
"compute.version",
8997
"compute.capabilities",
@@ -98,10 +106,21 @@ pub(crate) async fn discover_capabilities(
98106
"compute.dispatch.result",
99107
"compute.dispatch.forward",
100108
"compute.dispatch.capabilities",
109+
"compute.hardware.observe",
110+
"compute.hardware.distill",
111+
"compute.hardware.apply",
112+
"compute.hardware.share_recipe",
113+
"compute.hardware.auto_init",
114+
"compute.hardware.auto_init_all",
115+
"compute.hardware.status",
116+
"compute.hardware.vfio_devices",
117+
"compute.performance_surface.report",
118+
"compute.performance_surface.query",
119+
"compute.performance_surface.list",
120+
"compute.route.multi_unit",
101121
"gpu.query_info",
102122
"gpu.query_memory",
103123
"gpu.query_telemetry",
104-
"provenance.query",
105124
"gate.update",
106125
"gate.remove",
107126
"gate.list",
@@ -112,37 +131,130 @@ pub(crate) async fn discover_capabilities(
112131
"transport.open",
113132
"transport.stream",
114133
"transport.status",
115-
"compute.hardware.observe",
116-
"compute.hardware.distill",
117-
"compute.hardware.apply",
118-
"compute.hardware.share_recipe",
119-
"compute.hardware.auto_init",
120-
"compute.hardware.auto_init_all",
121-
"compute.hardware.status",
122-
"compute.hardware.vfio_devices",
134+
"shader.dispatch",
135+
"ember.list",
136+
"ember.status",
137+
"provenance.query",
123138
];
124139

125140
for m in &semantic_methods {
126-
if !direct_methods.contains(m) {
127-
direct_methods.push(m);
141+
if !methods.contains(m) {
142+
methods.push(m);
128143
}
129144
}
130-
direct_methods.sort_unstable();
145+
methods.sort_unstable();
146+
methods
147+
}
131148

132-
let capabilities = serde_json::json!({
149+
/// Wire Standard L2 `capabilities.list` response.
150+
///
151+
/// Returns `{primal, version, methods, provided_capabilities}` per
152+
/// `CAPABILITY_WIRE_STANDARD.md` v1.0.
153+
#[expect(
154+
clippy::unused_async,
155+
reason = "handler signature requires async for uniform dispatch"
156+
)]
157+
pub(crate) async fn capabilities_list(
158+
semantic_registry: &SemanticMethodRegistry,
159+
version: &str,
160+
) -> JsonRpcResult {
161+
let methods = all_callable_methods(semantic_registry);
162+
163+
Ok(serde_json::json!({
164+
"primal": toadstool_common::constants::PRIMAL_NAME,
165+
"version": version,
166+
"methods": methods,
167+
"provided_capabilities": [
168+
{
169+
"type": "compute",
170+
"methods": ["submit", "status", "result", "cancel", "list",
171+
"dispatch.submit", "dispatch.status", "dispatch.result",
172+
"dispatch.forward", "dispatch.capabilities",
173+
"hardware.observe", "hardware.distill", "hardware.apply",
174+
"hardware.share_recipe", "hardware.auto_init",
175+
"hardware.auto_init_all", "hardware.status",
176+
"hardware.vfio_devices",
177+
"performance_surface.report", "performance_surface.query",
178+
"performance_surface.list", "route.multi_unit",
179+
"health", "version", "capabilities", "discover_capabilities"],
180+
"version": version,
181+
"description": "GPU job queue, hardware dispatch, and performance routing"
182+
},
183+
{
184+
"type": "toadstool",
185+
"methods": ["submit_workload", "query_status", "cancel_workload",
186+
"list_workloads", "query_capabilities", "health", "version",
187+
"resources.estimate", "resources.validate_availability",
188+
"resources.suggest_optimizations"],
189+
"version": version,
190+
"description": "High-level workload executor (multi-runtime)"
191+
},
192+
{
193+
"type": "gpu",
194+
"methods": ["query_info", "query_memory", "query_telemetry"],
195+
"description": "GPU hardware info and telemetry"
196+
},
197+
{
198+
"type": "gate",
199+
"methods": ["update", "remove", "list", "route"],
200+
"description": "Distributed cross-gate routing"
201+
},
202+
{
203+
"type": "transport",
204+
"methods": ["discover", "list", "route", "open", "stream", "status"],
205+
"description": "Hardware transport (DRM, V4L2, serial)"
206+
},
207+
{
208+
"type": "shader",
209+
"methods": ["dispatch"],
210+
"description": "Sovereign shader dispatch (VFIO/DRM passthrough)"
211+
},
212+
{
213+
"type": "ember",
214+
"methods": ["list", "status"],
215+
"description": "glowPlug/ember GPU device lifecycle"
216+
}
217+
],
218+
"consumed_capabilities": [
219+
"security.sign",
220+
"security.verify",
221+
"storage.artifact.store",
222+
"storage.artifact.retrieve",
223+
"coordination.register",
224+
"coordination.discover"
225+
],
226+
"protocol": "jsonrpc-2.0",
227+
"transport": ["uds", "tcp"]
228+
}))
229+
}
230+
231+
/// Returns discovered capabilities including semantic methods.
232+
///
233+
/// Legacy `compute.discover_capabilities` — returns node capabilities
234+
/// and merged method list.
235+
#[expect(
236+
clippy::unused_async,
237+
reason = "handler signature requires async for uniform dispatch"
238+
)]
239+
pub(crate) async fn discover_capabilities(
240+
semantic_registry: &SemanticMethodRegistry,
241+
version: &str,
242+
) -> JsonRpcResult {
243+
let methods = all_callable_methods(semantic_registry);
244+
245+
Ok(serde_json::json!({
133246
"node_capabilities": [
134247
"compute", "workload", "orchestration",
135248
"gpu", "wasm", "container", "hardware_transport",
136249
"shader_dispatch", "hardware_learning"
137250
],
138-
"methods": direct_methods,
251+
"methods": methods,
139252
"version": version,
140253
"primal": toadstool_common::constants::PRIMAL_NAME
141-
});
142-
Ok(capabilities)
254+
}))
143255
}
144256

145-
/// Returns primal identity per `CAPABILITY_BASED_DISCOVERY_STANDARD.md`.
257+
/// Returns primal identity per Wire Standard L2 + `CAPABILITY_BASED_DISCOVERY_STANDARD.md`.
146258
///
147259
/// Every primal MUST implement `identity.get` so orchestrators and peers
148260
/// can discover name, version, capabilities, and protocol.
@@ -170,6 +282,8 @@ pub(crate) async fn identity_get(
170282
Ok(serde_json::json!({
171283
"primal": toadstool_common::constants::PRIMAL_NAME,
172284
"version": version,
285+
"domain": "compute",
286+
"license": "AGPL-3.0-or-later",
173287
"protocol": "JSON-RPC 2.0",
174288
"capabilities": capabilities,
175289
"methods": semantic_methods,
@@ -221,20 +335,25 @@ mod tests {
221335
use super::*;
222336

223337
#[tokio::test]
224-
async fn health_includes_version_uptime_and_error_count() {
338+
async fn health_includes_version_uptime_error_count_and_wire_status() {
225339
let ver = "unit-test-9.9.9";
226340
let start = Instant::now()
227341
.checked_sub(Duration::from_secs(2))
228342
.expect("instant");
229343
let errors = AtomicU64::new(7);
230344
let v = health(ver, start, &errors).await.expect("health ok");
231345
assert_eq!(v["healthy"], true);
346+
assert_eq!(
347+
v["status"], "alive",
348+
"Wire Standard L1: status must be 'alive'"
349+
);
232350
assert_eq!(v["version"], ver);
233351
assert!(v["uptime_secs"].as_u64().unwrap() >= 2);
234352
assert_eq!(v["error_count"], 7);
235353
errors.fetch_add(1, Ordering::Relaxed);
236354
let v2 = health(ver, start, &errors).await.expect("health ok");
237355
assert_eq!(v2["error_count"], 8);
356+
assert_eq!(v2["status"], "alive");
238357
}
239358

240359
#[tokio::test]
@@ -271,11 +390,69 @@ mod tests {
271390
}
272391

273392
#[tokio::test]
274-
async fn identity_get_lists_core_capabilities_and_methods() {
393+
async fn capabilities_list_returns_wire_standard_envelope() {
394+
let reg = SemanticMethodRegistry::new();
395+
let cap = capabilities_list(&reg, "wire-1.0")
396+
.await
397+
.expect("capabilities_list");
398+
assert_eq!(cap["primal"], toadstool_common::constants::PRIMAL_NAME);
399+
assert_eq!(cap["version"], "wire-1.0");
400+
401+
let methods = cap["methods"].as_array().expect("methods array");
402+
assert!(!methods.is_empty());
403+
let strs: Vec<&str> = methods.iter().map(|m| m.as_str().unwrap()).collect();
404+
let mut sorted = strs.clone();
405+
sorted.sort_unstable();
406+
assert_eq!(strs, sorted, "methods must be sorted");
407+
408+
assert!(
409+
strs.contains(&"capabilities.list"),
410+
"must advertise capabilities.list"
411+
);
412+
assert!(
413+
strs.contains(&"health.liveness"),
414+
"must advertise health.liveness"
415+
);
416+
assert!(
417+
strs.contains(&"identity.get"),
418+
"must advertise identity.get"
419+
);
420+
assert!(
421+
strs.contains(&"compute.submit"),
422+
"must advertise compute.submit"
423+
);
424+
assert!(
425+
strs.contains(&"shader.dispatch"),
426+
"must advertise shader.dispatch"
427+
);
428+
assert!(strs.contains(&"ember.list"), "must advertise ember.list");
429+
430+
let groups = cap["provided_capabilities"]
431+
.as_array()
432+
.expect("provided_capabilities");
433+
assert!(groups.len() >= 5, "should have multiple capability groups");
434+
let group_types: Vec<&str> = groups.iter().map(|g| g["type"].as_str().unwrap()).collect();
435+
assert!(group_types.contains(&"compute"));
436+
assert!(group_types.contains(&"toadstool"));
437+
assert!(group_types.contains(&"gpu"));
438+
assert!(group_types.contains(&"transport"));
439+
assert!(group_types.contains(&"shader"));
440+
441+
assert!(cap["consumed_capabilities"].as_array().is_some());
442+
assert_eq!(cap["protocol"], "jsonrpc-2.0");
443+
}
444+
445+
#[tokio::test]
446+
async fn identity_get_includes_domain_and_license() {
275447
let reg = SemanticMethodRegistry::new();
276448
let id = identity_get("id-ver", &reg).await.expect("identity_get");
277449
assert_eq!(id["primal"], toadstool_common::constants::PRIMAL_NAME);
278450
assert_eq!(id["version"], "id-ver");
451+
assert_eq!(id["domain"], "compute", "Wire Standard L2: domain field");
452+
assert_eq!(
453+
id["license"], "AGPL-3.0-or-later",
454+
"Wire Standard L2: license field"
455+
);
279456
assert_eq!(id["protocol"], "JSON-RPC 2.0");
280457
assert_eq!(id["transport"], "unix-socket");
281458
let sock = id["socket_name"].as_str().expect("socket_name");

crates/server/src/pure_jsonrpc/handler/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,10 @@ impl JsonRpcHandler {
173173
return core::health(&self.version, self.start_time, &self.error_count).await;
174174
}
175175
"compute.version" => return core::version_info(&self.version).await,
176-
"compute.capabilities"
177-
| "capabilities.list"
178-
| "capability.list"
179-
| "primal.capabilities" => return self.workload.query_capabilities().await,
176+
"capabilities.list" | "capability.list" | "primal.capabilities" => {
177+
return core::capabilities_list(&self.semantic_registry, &self.version).await;
178+
}
179+
"compute.capabilities" => return self.workload.query_capabilities().await,
180180
"compute.discover_capabilities" => {
181181
return core::discover_capabilities(&self.semantic_registry, &self.version).await;
182182
}

0 commit comments

Comments
 (0)