Skip to content

Enforce the LocalMotion contract before mutating the tree - #60

Merged
siddhss5 merged 1 commit into
mainfrom
fix/local-motion-contract
Sep 17, 2026
Merged

siddhss5 merged 1 commit into
mainfrom
fix/local-motion-contract

Conversation

@siddhss5

Copy link
Copy Markdown
Collaborator

Why

#54: the endpoint check ran after configurations were inserted and only when the payload was nonempty, so LocalMotion(configs=[], reached=True) on a nonzero motion reported success with the source node still current. Reproduced on main: _grow returned reached with a one-node tree, and solve returned a two-waypoint path whose only segment was never validated.

What

The whole LocalMotion is checked before the tree is touched, with one documented policy per case:

  • Zero-length motion (source is already the exact target): success, no node added, whatever the validator returned.
  • Structural violation (reached=True with no configurations on a nonzero motion, or a final configuration whose shape or value is not the exact target): raises the new MotionContractError. Rationale: this is a bug in the validator, and treating it as "not reached" would hide it.
  • Claimed success with an inadmissible configuration (custom validators; the default is built from admissibility): rejected whole, nothing stored, not reached.
  • Partial result (reached=False): the admissible prefix is stored, as before. Documented as deliberate so partial extensions keep their progress.

LocalMotion's docstring states the contract; changelog updated.

Fixes #54. Refs #17, #46, #47.

Test plan

Eight new tests in test_motion.py: empty-but-reached raises in direct growth (tree untouched), during bidirectional connection, and during shortcut smoothing (a validator honest for growth but lying for long shortcuts); a claimed success with a bad middle state stores nothing, not even its admissible first config; a partial result keeps its prefix; zero-length motions succeed without duplicates for both a target-returning and an empty payload; a wrong-shaped endpoint raises; the default validator never trips the checks over seeded plans with smoothing. One #46 test updated to the whole-rejection policy.

  • uv run pytest tests/: 276 passed, 1 skipped; ruff clean.

🤖 Generated with Claude Code

_extend_along_edge inserted a custom validator's configurations as it
went and only then checked the endpoint, and skipped that check when
the payload was empty. A validator returning reached=True with no
configurations on a nonzero motion therefore reported success with the
source node, breaking the meaning of reached established in #47, and a
malformed success could partially mutate the tree.

The whole LocalMotion is now checked first. A zero-length motion
succeeds without adding a node regardless of the payload. On a nonzero
motion, reached=True with no configurations or with a final
configuration that is not the exact target raises MotionContractError,
since that is a validator bug. For custom validators, a claimed success
containing an inadmissible configuration is rejected whole and nothing
is stored; a partial (reached=False) result keeps its admissible
prefix, which is documented as deliberate.

Tests cover the empty-but-reached payload in direct growth,
bidirectional connection, and shortcut smoothing; whole rejection of a
claimed success with a bad middle state; prefix retention for partial
results; zero-length motions; wrong-shaped endpoints; and that the
default validator never trips the checks.

Fixes #54. Refs #17, #46, #47.
@siddhss5
siddhss5 merged commit ca73d7b into main Sep 17, 2026
4 checks passed
@siddhss5
siddhss5 deleted the fix/local-motion-contract branch September 17, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enforce the LocalMotion endpoint contract before reporting reached

1 participant