Require ancestor consent for dynamic path ownership#51
Merged
jhiemstrawisc merged 2 commits intoMay 8, 2026
Conversation
Dynamic path resolution lets the deepest live sublot own a subtree, but that only works if hierarchy edges remain the source of authority. The old exact-path overlap check allowed a lot to carve out a subpath beneath another lot's recursive coverage without being in that lot's parent chain, which made ownership resolution disagree with the declared hierarchy. Treat recursive prefix coverage as a consent boundary. When a live path claim overlaps another lot's recursive coverage, the claiming lot must be a recursive descendant of every overlapping covering lot; when a recursive claim would cover an existing subpath, the covered lot must descend from the claimant. Parent, path, and MPA updates revalidate against the final transaction state so legitimate simultaneous rewires can succeed while removing an authority edge cannot leave a stale path claim behind. Storage lookup errors in ancestry checks are now reported as lookup failures rather than folded into "not an ancestor", keeping enforcement diagnostics precise and avoiding fail-open ambiguity.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR tightens Lotman’s dynamic path ownership model by enforcing “ancestor consent” when recursive path coverage overlaps, ensuring authority comes from the declared parent/child hierarchy and improving diagnostics for ancestry lookup failures.
Changes:
- Enforce descendancy-based rules for recursive path prefix overlaps (both “prefix-in” and “prefix-out”) and revalidate path legitimacy after transactional updates.
- Improve error reporting so ancestry storage lookup failures aren’t misreported as descendancy violations.
- Add/adjust tests to cover dynamic-ownership behavior and avoid new-rule collisions in existing exclusion tests.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/strict_hierarchy_tests.cpp | Updates a parent-rewire test to also move the child’s path in the same update to satisfy the new ownership rule. |
| test/main.cpp | Adjusts the exclusion-prefix edge-case test paths to avoid collisions under the new descendancy rule. |
| test/dynamic_ownership_tests.cpp | Adds a new test suite covering dynamic ownership + descendancy enforcement and diagnostics. |
| test/CMakeLists.txt | Adds the new dynamic ownership test file to the gtest target. |
| src/schemas.h | Updates schema descriptions to clarify exclude semantics under dynamic ownership. |
| src/lotman.h | Expands public API documentation to describe dynamic resolution and the descendancy rule. |
| src/lotman.cpp | Changes update flow to defer path revalidation until end-of-transaction when parents/paths are edited. |
| src/lotman_internal.h | Extends internal APIs for deferred path revalidation and adds ancestry-check helpers. |
| src/lotman_internal.cpp | Implements end-of-transaction path revalidation and updates overlap checking to include recursion state. |
| src/lotman_db.cpp | Implements descendancy-aware overlap detection and adds an ancestry walk helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
1792
to
+1794
| std::string path_to_check = (current_path != new_path) ? new_path : current_path; | ||
| auto overlap = check_path_temporal_overlap(lot_name, path_to_check, this_mpa->creation_time, | ||
| this_mpa->expiration_time); | ||
| auto overlap = check_path_temporal_overlap(lot_name, path_to_check, path_record.recursive != 0, | ||
| this_mpa->creation_time, this_mpa->expiration_time); |
Comment on lines
+1004
to
+1006
| " p.path = ?4 " // (1) exact | ||
| " OR (p.recursive = 1 AND p.path != ?4 AND ?4 LIKE p.path || '%') " // (2) prefix-in | ||
| " OR (?5 = 1 AND p.path != ?4 AND p.path LIKE ?4 || '%') " // (3) prefix-out (only if candidate recursive) |
Comment on lines
+1094
to
+1097
| for (const auto &node : frontier) { | ||
| auto parents = storage.select( | ||
| &db::Parent::parent, where(c(&db::Parent::lot_name) == node and c(&db::Parent::parent) != node)); | ||
| for (auto &p : parents) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dynamic path resolution lets the deepest live sublot own a subtree, but that only works if hierarchy edges remain the source of authority. The old exact-path overlap check allowed a lot to carve out a subpath beneath another lot's recursive coverage without being in that lot's parent chain, which made ownership resolution disagree with the declared hierarchy.
Treat recursive prefix coverage as a consent boundary. When a live path claim overlaps another lot's recursive coverage, the claiming lot must be a recursive descendant of every overlapping covering lot; when a recursive claim would cover an existing subpath, the covered lot must descend from the claimant. Parent, path, and MPA updates revalidate against the final transaction state so legitimate simultaneous rewires can succeed while removing an authority edge cannot leave a stale path claim behind.
Storage lookup errors in ancestry checks are now reported as lookup failures rather than folded into "not an ancestor", keeping enforcement diagnostics precise and avoiding fail-open ambiguity.