Repository navigation
Report store version conflicts as typed values, not parsed messages (closes #3032) - #3035
Conversation
…loses #3032) move_children_to_parent_in_tx and delete_subtree_atomic now return Ok(Err(VersionConflict { node_id, expected, actual })) instead of formatting the conflict into an anyhow message the service had to string-match. move_children_to_parent now reports the real expected and persisted versions (previously 0/0), and a zero-row gated DELETE on a child still at its expected version (no parent edge) is a plain error rather than a misreported conflict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d_rename The own-fields read moved into a validation helper when rename_schema_field was hardened, so the extends-chain-blindness guard no longer matched its allowlist entry and failed on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pragmatic Code Review — PR #3035Review Type: Initial Review SummaryThis is a clean, well-scoped change. Both store paths that encoded OCC conflicts in Requirements Check
Commit-safety check (the key risk of
|
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
- impl From<VersionConflict> for NodeServiceError; both service call sites now propagate it with ? instead of repeating the mapping closure - root-child store test asserts the missing-parent-edge error, not only that the error is not a conflict Skipped: a child deleted between pre-validation and the transaction now surfaces as QueryFailed rather than a (fabricated 0/0) conflict. Reporting it as a conflict would need an invented actual version; the window is narrow and the batch still rolls back. Revisit if the frontend needs a typed not-found for batch moves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review feedback addressedThis responds to the initial review, which recommended APPROVE and raised four optional suggestions. ✅ Addressed (2)
⏭️ Skipped (2)
🧪 Tests: 23 targeted core tests pass, clippy is clean, and the pre-push gate re-ran the full suite on push. Re-review decision: NO RE-REVIEW NEEDED. These are optional-suggestion changes with no logic change: a |
Summary
Closes #3032.
Two store paths used to report OCC conflicts inside
anyhowmessage text, and the service parsed that text to build the error:move_children_to_parent_in_tx"VERSION_CONFLICT: node '<id>' …"version_conflict(id, 0, 0)delete_subtree_atomic"version_conflict:<id>:<exp>:<act>"unwrap_orfallbacksBoth now return
Ok(Err(VersionConflict { node_id, expected, actual })), following the ADR-069 §2a convention thatupdate_node_with_version_check_in_txalready uses. The service maps that value toNodeServiceError::VersionConflictby type, and both string parsers are deleted.db::VersionConflict(exported fromdb). It implementsDisplayandError, so a caller can wrap it in ananyhowerror when it has to fail a transaction closure. The store-level test helper does this so its transaction rolls back.move_children_to_parent_in_txreads the child's persisted version inside the same transaction. It returns a conflict only if that version differs from the expected one. A child that is still at its expected version but has nohas_childedge now fails with a plain error, instead of being reported as a conflict.Errfrom itswith_transactionclosure, so those swaps roll back (there's a comment on this at the mapping site).Unrelated fix included so the pre-push gate passes
maincurrently failsschema_chain_blindness_guard_test. #3030 movedrename_schema_field's own-fields read intovalidate_schema_field_rename, but the allowlist entry added by #2946 still names the old function. The second commit points the entry at the new function. The justification text is unchanged, and so is the read itself.Tests
VersionConflictand checks the id, the expected version and the actual version. A new test checks that a root child at its correct version is not reported as a conflict.move_children_to_parentstale-version test, thedelete_nodeconflict test and the concurrent-write race test all check the realexpected_version/actual_version. In the race test, the losing move must report the writer'sversion + 1.test:all, the daemon build and e2e.🤖 Generated with Claude Code