`oxmpl` - sprint-005
Sprint 005 — Binding errors and resolution exposure (provisional)
Provisional scaffold for release 0.9.0 (E4), written 2026-10-08 so the release isn't lost. Nothing here is committed: the stories carry no
oxmpl/sprint/005tag until planning confirms them against the owner's stated availability. Dates are re-derived at the previous sprint's retro if it slipped.
- Dates (provisional): Thu 2026-12-10 → Wed 2026-12-30 (three weeks)
- Epic: E4 — Validation (0.9.0)
- Goal (provisional): Typed errors in both bindings with user exceptions re-raised; resolution exposed for all six spaces.
Provisional commitment
- S4.6 — Binding error hierarchy (size-M; after E3, S4.2)
- S4.8 — Resolution exposed in the bindings (size-M; after E3, S4.3)
Order: Either order. If S4.5 is done, S4.6's ConfigurationError can be wired to its "already in use" checks here.
Overlaps the year-end holidays — confirm availability at planning. Entry criteria: S4.2, S4.3 done (S4.5 recommended).
Blind pickup: how to (re)generate the tickets
Tickets for these stories already exist in TaskNotes (created 2026-10-08). Once a ticket exists, its TaskNotes details are authoritative and this page is only the regeneration spec. If a ticket is missing or was lost, recreate it from the spec below:
tasknotes_create_taskwith the title, projects, tags and priority shown. MCP quirks: create drops the description and defaultsscheduledto today, so follow withtasknotes_update_taskto setdetails(the body below) andscheduled: null.- Wire
blockedByby editing the task file's YAML frontmatter directly (no MCP tool exposes it). A story lists all upstream dependencies, including done ones. - Verify with
tasknotes_get_task:isBlocked: trueon the story while its upstreams are open, andisBlockingon each upstream. After any laterupdate_task, re-check thatblockedBysurvived. - If the epic E4 is missing, recreate it first from the spec in
oxmpl- sprint-003 § Epic E4. - At this sprint's planning (owner confirms availability): add the
oxmpl/sprint/NNNtag, setscheduled/dueto the sprint dates, removesprint-status: provisionalfrom this page's frontmatter, and rewrite the "Provisional commitment" heading as "Commitment".
Conventions: oxmpl - planning. Repo context: ADR docs/planning/adr/0003-motion-validation.md (decisions), CONTEXT.md (glossary). 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.
Story specs
S4.6 — Binding error hierarchy (size-M)
# TaskNotes task — file: task_notes/tasks/S4.6 — Binding error hierarchy.md
title: "S4.6 — Binding error hierarchy"
status: open
priority: normal
projects: ["[[E4 Validation — 0.9.0]]"]
tags: [oxmpl, oxmpl/epic, oxmpl/story/size-M] # add oxmpl/sprint/005 only when committed at planning
scheduled: null # set to sprint start when committed
due: null # set to sprint end when committed
blockedBy: # wire in frontmatter by hand (no MCP tool exposes it)
- uid: "[[E3 Toolchain refresh — 0.8.0]]"
reltype: FINISHTOSTART
- uid: "[[S4.2 — Fallible, thread-safe validation core]]"
reltype: FINISHTOSTART
details body:
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
S4.8 — Resolution exposed in the bindings (size-M)
# TaskNotes task — file: task_notes/tasks/S4.8 — Resolution exposed in the bindings.md
title: "S4.8 — Resolution exposed in the bindings"
status: open
priority: normal
projects: ["[[E4 Validation — 0.9.0]]"]
tags: [oxmpl, oxmpl/epic, oxmpl/story/size-M] # add oxmpl/sprint/005 only when committed at planning
scheduled: null # set to sprint start when committed
due: null # set to sprint end when committed
blockedBy: # wire in frontmatter by hand (no MCP tool exposes it)
- uid: "[[E3 Toolchain refresh — 0.8.0]]"
reltype: FINISHTOSTART
- uid: "[[S4.3 — Space resolution valid segment count]]"
reltype: FINISHTOSTART
details body:
Intent
Only RealVector, SO2 and SO3 expose a resolution setter in either binding, so SE2/SE3/compound users cannot follow the 0.9.0 migration note ("set the fraction to 0.005"). Python exposes no interpolate or segment length at all. Expose resolution fully so users can tune spaces and so user motion validators (S4.7) can work at the space's resolution without re-implementing its maths.
Decisions (grilling Q15)
- All six spaces (RealVector, SO2, SO3, SE2, SE3, Compound) in both bindings expose: the fraction setter,
longest_valid_segment_length,valid_segment_count(s1, s2),interpolate(s1, s2, t). - JS
setLongestValidLineSegmentFractionis renamedsetLongestValidSegmentFraction(matches Rust, Python and OMPL). - Setters respect S4.5's freeze rule (raise if the space is already in use).
- SE2/SE3/compound fraction semantics: setting the fraction on a composite space sets it on every component (as OMPL's
CompoundStateSpace::setLongestValidSegmentFraction); confirm against the Rust API from S4.3 and add a Rust setter if missing.
Current gaps (as of c558556)
| Space | Py setter | Py length | Py interpolate | JS setter | JS length | JS interpolate |
|---|---|---|---|---|---|---|
| RealVector | yes | no | no | yes (old name) | yes | yes |
| SO2 | yes | no | no | yes (old name) | yes | yes |
| SO3 | yes | no | no | yes (old name) | yes | yes |
| SE2 | no | no | no | no | yes | yes |
| SE3 | no | no | no | no | yes | yes |
| Compound | no | no | no | no | yes | yes |
Acceptance criteria
Notes
Cut candidates if the sprint runs short: interpolate and valid_segment_count (keep the setter — the migration note depends on it). Both can be added later without breaking anyone.
Progress log
- 2026-10-08 — Scaffolded as provisional during sprint-002 planning (owner request); expanded the same day into a full regeneration spec (owner request: "enough that a blind pickup can generate all the tickets"). Not yet planned or committed.
Review & retro
At sprint end: done / carried over / dropped, what was over- or underestimated, and at most one process change for the next sprint.