S4.2 — Fallible, thread-safe validation core
Intent
User-supplied validation code can fail, and today that failure is invisible. Probe against oxmpl-py 0.7.0 (2026-10-08): a Python checker that raised was printed and treated as "invalid", so solve() still returned a path; KeyboardInterrupt was swallowed; a typo (s.valuez) produced 286k traceback lines over the full timeout and then a misleading "No solution found". OMPL's Python bindings raise immediately because C++ exceptions unwind through the planner; Rust has no such channel (and the wasm build is panic="abort"), so the error must be in the signature. Make failure a first-class error and make the validation traits thread-safe in one breaking change.
Decisions (ADR-0003; grilling Q4/Q5/Q6/Q7/Q18)
UserError = Box<dyn std::error::Error + Send + Sync>: any Rust error converts with?; bindings box the originalPyErr/JsValueso S4.6 can re-raise it.- Fallible:
StateValidityChecker::is_valid -> Result<bool, UserError>,Goal::is_satisfied -> Result<bool, UserError>,GoalRegion::distance_goal -> Result<f64, UserError>.GoalSampleableRegion::sample_goalkeeps its signature (alreadyResult<S, StateSamplingError>). PlanningError: addUser(UserError); removePartialEq(boxed errors are not comparable; tests already usematches!); add#[non_exhaustive].StateSamplingError: addUser(UserError); add#[non_exhaustive](and dropPartialEqifUsermakes it impossible).- Thread safety:
StateValidityChecker,StateSpace(includingAnyStateSpaceand the boxed compound subspaces), and the goal traits all becomeSend + Sync. Needed because the motion validator (S4.4) holds the space and checker and must beSend + Sync; adding the bound later would be a second breaking change. - JS:
js_sys::Function/JsValueare neverSend/Sync. Adapters useunsafe impl Send + Sync, which is sound only on single-threaded wasm32; guard with#[cfg(target_feature = "atomics")] compile_error!(...). - Python:
Py<T>is alreadySend + Sync(pyo3), so no unsafe needed. - Planners abort on the first user error with
PlanningError::User(..); no retry, no logging-and-continue.
Acceptance criteria
Tasks
Risks
- Highest: the
Send + Synccascade throughBox<dyn AnyStateSpace>(compound_state_space.rs:53) may collide with the ADR-0002 two-tierState/AnyStatesplit. If it spreads beyond adding bounds, stop and raise it with the owner before redesigning. ?inside iterator chains in rrt_star.rs needs local refactors.
Source
Agreed in the senior-engineer grilling 2026-10-08 (decisions: repo docs/planning/adr/0003-motion-validation.md; glossary: CONTEXT.md). Regeneration spec: oxmpl - sprint-003. Line references are as of origin/main c558556 (2026-10-08), before E3. E3 renames things (rand Rng→RngExt, PyO3 with_gil→attach, edition 2024), so re-grep before trusting a line number.