S4.6 — Binding error hierarchy
Intent
Python callers get a bare Exception with a message new_err(e.to_string()) — 25 sites across the four planners) and JS callers get thrown strings (map_err(|e| e.to_string()) — 30 sites. Give both bindings typed errors, and re-raise users' own exceptions unwrapped. Absorbs the backlog item "Robust binding errors" and resolves the TODO callouts at docs/src/python_api.md:129 and docs/src/js_api.md:179.
Decisions (ADR-0003; grilling Q16/Q18)
- Python: base
OxmplError(Exception)with subclassesPlanningTimeout,NoSolutionFound,InvalidStartState,SamplingError,PlannerUninitialised,ConfigurationError. Map eachPlanningError/StateSamplingErrorvariant to one class; non-exhaustive enums need a catch-all →OxmplError. - JS:
class OxmplError extends Errorwith a stringkindfield ("Timeout","NoSolutionFound","InvalidStartState","Sampling","PlannerUninitialised","Configuration"). PlanningError::User/StateSamplingError::User(S4.2) carry the originalPyErr/JsValue; the binding re-raises it unchanged (traceback intact;KeyboardInterruptstaysKeyboardInterrupt), as OMPL's Python bindings do.ConfigurationErroris raised by S4.5's "already in use" checks and S4.7's "missing check_motion" check.
Acceptance criteria
Tasks
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-005. 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.