ruvnet/RuView · error
invalid geometry {path}: {e} (expected a JSON array of NodeG
Error message
invalid geometry {path}: {e} (expected a JSON array of NodeGeometry records) What it means
The geometry file was read but its JSON did not deserialize into Vec<NodeGeometry> — the message spells out the expected shape ('a JSON array of NodeGeometry records'). Causes: a JSON object instead of a top-level array, missing or renamed NodeGeometry fields, wrong types, or malformed JSON.
Source
Thrown at v2/crates/wifi-densepose-cli/src/room.rs:254
/// Execute `train-room`.
///
/// If the enrollment session carries a transceiver-geometry snapshot (recorded
/// at enroll time or supplied here via `--geometry`), it is threaded into the
/// bank (ADR-152 §2.1.1); a geometry-free enrollment still trains a valid bank.
pub async fn train_room(args: TrainRoomArgs) -> Result<()> {
let raw = std::fs::read_to_string(&args.enrollment)
.map_err(|e| anyhow::anyhow!("cannot read {}: {e} — run `enroll` first", args.enrollment))?;
let mut data: EnrollmentData =
serde_json::from_str(&raw).map_err(|e| anyhow::anyhow!("invalid enrollment: {e}"))?;
if data.anchors.is_empty() {
bail!("no accepted anchors in {} — re-run enroll", args.enrollment);
}
if let Some(path) = &args.geometry {
let graw = std::fs::read_to_string(path)
.map_err(|e| anyhow::anyhow!("cannot read geometry {path}: {e}"))?;
let geometry: Vec<NodeGeometry> = serde_json::from_str(&graw).map_err(|e| {
anyhow::anyhow!("invalid geometry {path}: {e} (expected a JSON array of NodeGeometry records)")
})?;
data.session.record_geometry(geometry, now_unix());
}
let mut bank = SpecialistBank::train(&data.room_id, &data.baseline_id, &data.anchors, now_unix())
.map_err(|e| anyhow::anyhow!("training failed: {e}"))?;
match data.session.geometry() {
Some(g) if !g.is_empty() => {
bank = bank.with_geometry(g.to_vec());
eprintln!(
"[train-room] geometry: {} node(s) snapshotted into the bank (ADR-152 §2.1.1)",
bank.geometry.len()
);
}
_ => eprintln!(
"[train-room] no transceiver geometry recorded — bank will not support geometry conditioning (ADR-152 §2.1.2)"
),
}View on GitHub (pinned to 4685618388)
Solutions
- Wrap records in a top-level array and match NodeGeometry field names exactly as the struct's serde attributes define them
- Verify with a quick deserialization probe (serde_json::from_str::<Vec<NodeGeometry>>) before invoking the CLI
- Write a converter for genuinely different survey formats instead of ad-hoc edits
- Omit --geometry to proceed without geometry conditioning
Example fix
// before
{ "node_id": "n1", "position_m": [1.0, 2.0] }
// after
[ { "node_id": "n1", "position_m": [1.0, 2.0] }, { "node_id": "n2", "position_m": [3.5, 0.5] } ] Defensive patterns
Strategy: validation
Validate before calling
fn geometry_shape_ok(raw: &str) -> bool {
serde_json::from_str::<Vec<NodeGeometry>>(raw).is_ok()
} Type guard
fn as_geometry(raw: &str) -> Option<Vec<NodeGeometry>> {
serde_json::from_str(raw).ok()
} Prevention
- Generate geometry files with the same serde structs that consume them
- Validate geometry JSON in CI for every surveyed room snapshot
- Remember the top level must be an array, not a single object
When it happens
Trigger: Passing a single object instead of an array; exporting geometry from survey tooling whose field names differ from NodeGeometry's serde schema; truncated files; passing the enrollment JSON as --geometry.
Common situations: Hand-authored geometry from site surveys; field-name drift (node identifiers, position keys) between producer tools and the NodeGeometry struct.
Related errors
- invalid enrollment: {e}
- serialize: {e}
- {path}: each node needs 'id' and 'position_m' [x,y,z]
- Calibration bundle {path} missing key {key!r}
- manifest must be an object with a 'markers' array
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/40d1e8c58549b1ec.
Report an issue: GitHub.