Slice B: update_node_avps/2 (merge/upsert + mutate/1 grammar kind)#49
Merged
Conversation
Brainstormed design for graphdb_mgr:update_node_avps/2 (the last not_implemented stub of its pair): merge/upsert semantics with a value-less-map delete signal (no new symbol; value => undefined stays a real declared-but-unbound entry, preserving slice C), order-preserving in-place upsert to honor the name-AVP-at-head convention, the three-tier seam layering (tier-1 in-txn primitive + tier-2 wrapper + mutate/1 grammar entry), guard placement under the no-gen_server-call-in-txn invariant, and the test plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Four-task subagent-driven plan: pure AVP merge/validate helpers, tier-1
in-txn primitive + tier-2 wrapper (replacing the not_implemented stub),
the {update_node_avps, Nref, AVPs} mutate/1 grammar kind, and docs/TASKS
status. Grounded in the real mutate/1 machinery and the set_retired
pattern; all four CT registration points and the clause-terminator flips
are spelled out.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
apply_avp_updates/2 (order-preserving upsert + value-less-map delete) and validate_avp_updates/1, with EUnit coverage. Pure functions only; no Mnesia. Foundation for the tier-1 primitive and mutate/1 grammar entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Replace the not_implemented stub with the merge/upsert write op: tier-1 update_node_avps_in_txn/3 (node-existence, attribute-existence on upserts, retired-marker guards; order-preserving merge) and the tier-2 wrapper owning one transaction/1, mirroring set_retired. Client-side well-formedness short-circuits before the gen_server call. Updates the pre-existing category-guard test (nref 6 now hits the permanent-tier guard). New update_avps CT group covers round-trip, delete, undefined- retained, every guard, and single-call atomicity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Add {update_node_avps, Nref, AVPs} as a fourth batch mutation kind:
validate_mutation runs the pure well-formedness + permanent-tier guards in
phase 1; dispatch composes update_node_avps_in_txn/3 with the RetAttr
already resolved by run_mutations. CT covers single, mixed-with-add_rel,
whole-batch rollback, and static malformed rejection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Narrow the not_implemented note to delete_node, document update_node_avps in the graphdb_mgr guides, add the fourth mutate/1 grammar kind, and mark slice B IMPLEMENTED in TASKS.md (trimming it from the mutate/1 deferred grammar-extension list). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Add mutate_update_avps_not_found CT case exercising the tier-1 mnesia:abort(not_found) branch via the batch path (the solo path short-circuits at the category guard). Reword the delete_node handle_call comment so it no longer implies update_node_avps is unimplemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Record the pre-existing low-priority follow-up surfaced by slice B's final review: a category node rejects with category_nodes_are_immutable on the solo update_node_avps path but permanent_node_immutable through mutate/1 (same as retire/unretire). Both refuse; only the reason atom differs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
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.
Slice B —
update_node_avps/2Implements
graphdb_mgr:update_node_avps/2(previously anot_implementedstub) as an atomic merge over a node's
attribute_value_pairs, through theexisting three-tier write-path transaction seam, and exposes it as a fourth
mutate/1batch mutation kind.The last
not_implementedstub of its pair ingraphdb_mgr(onlydelete_noderemains). Unblocks the AVP-edit halves of slice C (templateattribute list) and slice E (relationship-AVP edit).
Semantics
valuekey replaces the matchingattribute in place (preserving list position), or appends if new.
valuekey removes that attribute(no-op if absent). No new sentinel: the value-less map shape was unused
anywhere in the system.
value => undefinedis a real upsert (a declared-but-unbound entry, sliceC's case), never a delete.
Layering (3-tier seam)
update_node_avps_in_txn/3— bare-mnesia, runs inside a caller'stxn,
mnesia:abort/1on failure, exported for composition.update_node_avps/2— owns onetransaction/1; runs theclient-side + pre-txn guards outside the txn (
RetAttrresolved outside —the load-bearing no-gen_server-call-in-txn invariant).
mutate/1—{update_node_avps, Nref, AVPs}composes the tier-1primitive directly into the batch transaction.
Guards
Category-immutable, permanent-tier, well-formedness (client-side),
node-existence, attribute-existence (upserts only), and retired-marker (the
retiredstate stays behindretire_node/unretire_node). Bare abortreasons; whole-batch rollback in
mutate/1.Tests
480 CT + 122 EUnit, zero warnings. New: 17 pure/client-side EUnit + an
update_avpsCT group (11 cases incl. round-trip, delete, undefined-retained,every guard, single-call + whole-batch atomicity, head-position preservation)
mutate-group cases.Docs
docs/designs/slice-b-update-node-avps-design.md,docs/superpowers/plans/2026-06-25-slice-b-update-node-avps.md;TASKS.mdmarked IMPLEMENTED; both
CLAUDE.mdguides and the module header updated.Note (pre-existing, not introduced here)
A category node rejects via
update_node_avps/2withcategory_nodes_are_immutableon the solo path butpermanent_node_immutablethrough
mutate/1(same divergence already present forretire_node/unretire_nodeinmutate/1). Left as-is; can be normalized ina follow-up if desired.
🤖 Generated with Claude Code