Skip to content

Commit 13ca232

Browse files
author
eastgate
committed
v2.93: resolve GAP-MATRIX-07 (critical), GAP-MATRIX-01b, GAP-MATRIX-02
GAP-MATRIX-07 (Critical — proxy forwarding broken): TransportEndpoint::parse() now handles unix:// URI scheme. Previously, unix:///path strings (from display_string() round-trips or external capability.register calls) contained ':' and were misrouted to TCP parsing, creating broken PathBuf values. All capability.call forwarding through discovered endpoints now works end-to-end. GAP-MATRIX-01b (Medium — BearDog Format E unrecognized): Added Format E to capability parser: result.provided_capabilities [{type: "security", methods: ["sign", "verify", ...]}]. Emits both the group type ("security") and qualified methods ("security.sign"). Both cap_probe.rs canonical parser and ai_advisor.rs mirror updated. BearDog's 9 capability groups now register correctly. GAP-MATRIX-02 (Medium — TOML graph parser rejection): GraphDefinition.name and .version now #[serde(default)], matching the neural_graph::Graph parser which already defaults missing fields. tower_atomic_bootstrap.toml (which omits name/version) now parses through both the DeploymentGraph and neural_graph code paths. Tests: 7,654 passing (+5 new), 0 failed, clippy PASS. Made-with: Cursor
1 parent 3cfeeec commit 13ca232

5 files changed

Lines changed: 163 additions & 7 deletions

File tree

crates/biomeos-core/src/socket_discovery/cap_probe.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,16 @@ pub async fn probe_unix_socket_capabilities_list(socket_path: impl AsRef<Path>)
9090

9191
/// Parse capability names from a `capabilities.list` / `capability.list` JSON-RPC response.
9292
///
93-
/// Handles all 4 ecosystem wire formats (aligned with primalSpring's
93+
/// Handles all 5 ecosystem wire formats (aligned with primalSpring's
9494
/// `extract_capability_names` reference parser):
9595
///
9696
/// - **Format A** — `result` is a flat string array: `["crypto.sign", ...]`
9797
/// - **Format B** — `result` is an object array: `[{"method": "crypto.sign"}]`
9898
/// (also accepts `{"name": ...}` for `result.capabilities` sub-arrays)
9999
/// - **Format C** — `result.method_info`: `[{"name": "crypto.sign", ...}]`
100100
/// - **Format D** — `result.semantic_mappings`: `{"crypto": {"sign": {}}}`
101+
/// - **Format E** — `result.provided_capabilities`: `[{"type": "security", "methods": [...]}]`
102+
/// (BearDog wire format — each entry is a capability group with typed methods)
101103
///
102104
/// Legacy shapes (`result.capabilities`, `result.methods`) are tried first
103105
/// for backwards compatibility.
@@ -162,6 +164,33 @@ pub fn extract_capabilities_from_response(resp: &serde_json::Value) -> Vec<Strin
162164
}
163165
}
164166

167+
// --- Format E: result.provided_capabilities [{type: "security", methods: [...]}] ---
168+
// BearDog wire format: each entry is a capability group with a type and method list.
169+
// Emits both the group type ("security") and qualified methods ("security.encrypt").
170+
if let Some(groups) = result["provided_capabilities"].as_array() {
171+
let parsed: Vec<String> = groups
172+
.iter()
173+
.flat_map(|group| {
174+
let cap_type = group["type"].as_str().unwrap_or_default();
175+
let mut names = Vec::new();
176+
if !cap_type.is_empty() {
177+
names.push(cap_type.to_string());
178+
}
179+
if let Some(methods) = group["methods"].as_array() {
180+
for m in methods {
181+
if let Some(method_name) = m.as_str() {
182+
names.push(format!("{cap_type}.{method_name}"));
183+
}
184+
}
185+
}
186+
names
187+
})
188+
.collect();
189+
if !parsed.is_empty() {
190+
return parsed;
191+
}
192+
}
193+
165194
warn!(
166195
"Unrecognized capabilities.list response shape: {}",
167196
serde_json::to_string(resp).unwrap_or_default()
@@ -376,6 +405,66 @@ mod tests {
376405
assert!(extract_capabilities_from_response(&resp).is_empty());
377406
}
378407

408+
// ── Format E: provided_capabilities [{type, methods}] (BearDog wire format) ──
409+
410+
#[test]
411+
fn format_e_provided_capabilities_beardog() {
412+
let resp = json!({
413+
"jsonrpc": "2.0",
414+
"id": 1,
415+
"result": {
416+
"primal": "beardog",
417+
"version": "0.9.0",
418+
"provided_capabilities": [
419+
{
420+
"type": "security",
421+
"methods": ["sign", "verify", "encrypt", "decrypt"],
422+
"version": "1.0.0"
423+
},
424+
{
425+
"type": "crypto",
426+
"methods": ["blake3_hash", "hmac_sha256"],
427+
"version": "1.0.0"
428+
},
429+
{
430+
"type": "beacon",
431+
"methods": ["generate", "get_id"],
432+
"version": "1.0.0"
433+
}
434+
]
435+
}
436+
});
437+
let mut caps = extract_capabilities_from_response(&resp);
438+
caps.sort();
439+
assert_eq!(caps, vec![
440+
"beacon",
441+
"beacon.generate",
442+
"beacon.get_id",
443+
"crypto",
444+
"crypto.blake3_hash",
445+
"crypto.hmac_sha256",
446+
"security",
447+
"security.decrypt",
448+
"security.encrypt",
449+
"security.sign",
450+
"security.verify",
451+
]);
452+
}
453+
454+
#[test]
455+
fn format_e_provided_capabilities_type_only() {
456+
let resp = json!({
457+
"result": {
458+
"provided_capabilities": [
459+
{"type": "storage"},
460+
{"type": "compute"}
461+
]
462+
}
463+
});
464+
let caps = extract_capabilities_from_response(&resp);
465+
assert_eq!(caps, vec!["storage", "compute"]);
466+
}
467+
379468
// ── Socket probe tests ──
380469

381470
#[tokio::test]

crates/biomeos-core/src/socket_discovery/transport.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,15 @@ impl TransportEndpoint {
106106
});
107107
}
108108

109+
// Unix socket: explicit unix:// prefix (round-trips from display_string())
110+
if let Some(stripped) = value.strip_prefix("unix://") {
111+
return Some(Self::UnixSocket {
112+
path: PathBuf::from(stripped),
113+
});
114+
}
115+
109116
// HTTP JSON-RPC: explicit prefix
110117
if let Some(stripped) = value.strip_prefix("http://") {
111-
// Strip trailing /jsonrpc if present
112118
let stripped = stripped.strip_suffix("/jsonrpc").unwrap_or(stripped);
113119
return Self::parse_http(stripped);
114120
}
@@ -250,6 +256,28 @@ mod tests {
250256
assert_eq!(abstract_sock.display_string(), "abstract://@biomeos_test");
251257
}
252258

259+
#[test]
260+
fn test_parse_unix_scheme() {
261+
let endpoint =
262+
TransportEndpoint::parse("unix:///run/biomeos/beardog-abc123.sock").unwrap();
263+
if let TransportEndpoint::UnixSocket { path } = endpoint {
264+
assert_eq!(path, PathBuf::from("/run/biomeos/beardog-abc123.sock"));
265+
} else {
266+
panic!("Expected UnixSocket, got {endpoint:?}");
267+
}
268+
}
269+
270+
#[test]
271+
fn test_parse_unix_scheme_roundtrip() {
272+
let original = TransportEndpoint::UnixSocket {
273+
path: PathBuf::from("/tmp/test.sock"),
274+
};
275+
let display = original.display_string();
276+
assert_eq!(display, "unix:///tmp/test.sock");
277+
let parsed = TransportEndpoint::parse(&display).unwrap();
278+
assert_eq!(original, parsed);
279+
}
280+
253281
#[test]
254282
fn test_parse_invalid() {
255283
assert!(TransportEndpoint::parse("").is_none());

crates/biomeos-graph/src/ai_advisor.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ async fn probe_capabilities_list(socket_path: &Path) -> Vec<String> {
130130
extract_capabilities_from_list_response(&resp)
131131
}
132132

133-
/// Parse capability names from a JSON-RPC response, handling all 4 ecosystem wire formats.
133+
/// Parse capability names from a JSON-RPC response, handling all 5 ecosystem wire formats.
134134
///
135135
/// Mirrors the canonical parser in `biomeos_core::socket_discovery::cap_probe`.
136136
fn extract_capabilities_from_list_response(resp: &serde_json::Value) -> Vec<String> {
@@ -187,6 +187,30 @@ fn extract_capabilities_from_list_response(resp: &serde_json::Value) -> Vec<Stri
187187
return parsed;
188188
}
189189
}
190+
// Format E: result.provided_capabilities [{type: "security", methods: [...]}]
191+
if let Some(groups) = result["provided_capabilities"].as_array() {
192+
let parsed: Vec<String> = groups
193+
.iter()
194+
.flat_map(|group| {
195+
let cap_type = group["type"].as_str().unwrap_or_default();
196+
let mut names = Vec::new();
197+
if !cap_type.is_empty() {
198+
names.push(cap_type.to_string());
199+
}
200+
if let Some(methods) = group["methods"].as_array() {
201+
for m in methods {
202+
if let Some(method_name) = m.as_str() {
203+
names.push(format!("{cap_type}.{method_name}"));
204+
}
205+
}
206+
}
207+
names
208+
})
209+
.collect();
210+
if !parsed.is_empty() {
211+
return parsed;
212+
}
213+
}
190214
vec![]
191215
}
192216

crates/biomeos-graph/src/graph.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@ pub struct GraphDefinition {
8181
/// Unique identifier for the graph
8282
pub id: GraphId,
8383

84-
/// Human-readable name
84+
/// Human-readable name (defaults to graph id if absent)
85+
#[serde(default)]
8586
pub name: String,
8687

87-
/// Semantic version
88+
/// Semantic version (defaults to "0.0.0" if absent)
89+
#[serde(default)]
8890
pub version: String,
8991

9092
/// Description of what this graph does

crates/biomeos-graph/src/loader.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,29 @@ mod tests {
191191

192192
#[test]
193193
fn test_missing_required_fields() {
194-
// Missing name field
194+
// Missing id field (the only truly required field)
195195
let toml = r#"
196196
[graph]
197-
id = "missing-name"
197+
name = "has-name-but-no-id"
198198
version = "1.0.0"
199199
"#;
200200
let result = GraphLoader::from_str(toml, None);
201201
assert!(result.is_err());
202202
}
203203

204+
#[test]
205+
fn test_name_and_version_default_when_absent() {
206+
let toml = r#"
207+
[graph]
208+
id = "minimal-graph"
209+
"#;
210+
let result = GraphLoader::from_str(toml, None);
211+
assert!(result.is_ok(), "name and version should default: {result:?}");
212+
let graph = result.unwrap();
213+
assert!(graph.definition.name.is_empty());
214+
assert!(graph.definition.version.is_empty());
215+
}
216+
204217
#[test]
205218
fn test_from_str_with_source_path() {
206219
let toml = r#"

0 commit comments

Comments
 (0)