Atomic add_relationship: collapse five transactions into one (PR 2/2)#45
Merged
Conversation
Collapse do_add_relationship/7's five transactions into one for TOCTOU isolation. Grep-confirmed the four phase helpers are single-use → convert in place; do_class_of/1 has a public caller → add class_of_in_txn/1 twin. Spends PR 1's tier-1 primitives. Behaviour-preserving; +2 error-atom tests (source/target_has_no_class), no atomicity/race test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
3 TDD tasks: (1) characterization tests for source/target_has_no_class; (2) collapse do_add_relationship/7 into one transaction (in-txn phase helpers + class_of_in_txn/1 + build_connection_rows split); (3) docs incl. deferred default-template name-search convergence task. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Characterization tests for the two previously-uncovered class-resolution error atoms, locked in before the atomic-collapse refactor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Validate endpoints, resolve class/template scope, and write the two directed rows in one graphdb_mgr:transaction/1 (TOCTOU-isolated). Convert the four single-use phase helpers in place to in-txn (abort-based) form; add private class_of_in_txn/1 (do_class_of/1 keeps its own txn for its public caller); split build_connection_rows into /6 (allocates) + /7 (pure) so the rel-id pair is allocated up-front outside the txn. Spends PR 1's tier-1 primitives. Behaviour-preserving. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Mark the Atomic add_relationship follow-up implemented; add the deferred default-template name-search convergence task; note add_relationship's single-transaction behaviour in the graphdb worker doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Final-review nit: do_default_template/1 (not default_template/1) is what already wraps its own transaction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
david-w-t
commented
Jun 21, 2026
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.
Atomic
add_relationship(PR 2 of 2)Collapses
graphdb_instance:do_add_relationship/7's five separate Mnesiatransactions into one, spending the tier-1 in-transaction read primitives
shipped in PR 1 (#44). This is the second and final slice of the Atomic
add_relationshiptransaction-seam follow-up.The honest framing
The collapse fixes no bug — only the final phase writes today, and both
directed rows already write together in one transaction, so there is no
partial-write window. What it buys is TOCTOU isolation: endpoint
validation, class/template resolution, scope check, and the row write now
share one consistent Mnesia snapshot, closing the window where another
process retires an endpoint, deletes a class, or changes a template between
validation and write. TOCTOU isolation is a race property — it has no
deterministic test, and none was written.
What changed
validate_arc_endpoints,resolve_arc_classes,resolve_template,validate_template_scope) areconverted in place to in-transaction (
mnesia:abort-based) form.class_of_in_txn/1added as the add-don't-rewrap twin ofdo_class_of/1(which keeps its own txn for its publicclass_ofcaller).build_connection_rowssplit into/6(allocates) +/7(pure); therel-id pair is allocated up-front, outside the transaction (gen_server
calls must never run inside an mnesia fun).
do_add_relationship/7rewritten as onegraphdb_mgr:transaction/1:validate → resolve classes → resolve template → validate scope → write.
Behaviour preservation
Every abort term is byte-identical to the pre-collapse
{error, Reason}return (
transaction/1re-adds the{error, _}wrapper); phase order ispreserved. The two previously-uncovered class-resolution atoms
(
source_has_no_class,target_has_no_class) gain characterization tests.Tests
446 CT + 105 EUnit = 551 green, zero warnings. The existing
add_relationshipsuite is unchanged (the behaviour-preservation proof);+2 new instance CT cases.
Follow-ups
TASKS.mdnow tracks a deferred Converge default-template name searchcleanup (the
do_find_template_by_name/2↔default_template_in_txn/1duplication) and the still-open Batch
mutate([Mutation])tier-3 entrypoint.
Design
docs/designs/atomic-add-relationship-design.md; plandocs/superpowers/plans/2026-06-21-atomic-add-relationship.md.🤖 Generated with Claude Code