From dfe46ef6cf9d0cf532055da985b07bdba5439a37 Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 05:35:09 -0400 Subject: [PATCH 1/8] feat: add graphdb_nrefs.hrl scaffold nref macro catalog Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/include/graphdb_nrefs.hrl | 74 ++++++++++++++++++++++++++ apps/graphdb/src/graphdb_bootstrap.erl | 5 ++ 2 files changed, 79 insertions(+) create mode 100644 apps/graphdb/include/graphdb_nrefs.hrl diff --git a/apps/graphdb/include/graphdb_nrefs.hrl b/apps/graphdb/include/graphdb_nrefs.hrl new file mode 100644 index 0000000..6f921e7 --- /dev/null +++ b/apps/graphdb/include/graphdb_nrefs.hrl @@ -0,0 +1,74 @@ +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +%% graphdb_nrefs.hrl -- Compile-time names for immutable scaffold nrefs. +%% +%% Scaffold nrefs 1-35 are written once at bootstrap and never reallocated. +%% Changing any value here requires re-bootstrapping the environment. +%% All values correspond directly to bootstrap.terms entries. +%% +%% nref_english (10000) is a permanent seed in the 10000-99999 tier. +%%--------------------------------------------------------------------- + +%% -- Top-level categories (scaffold 1-5) ------------------------------ +-define(NREF_ROOT, 1). +-define(NREF_ATTRIBUTES, 2). +-define(NREF_CLASSES, 3). +-define(NREF_LANGUAGES, 4). +-define(NREF_PROJECTS, 5). + +%% -- Attribute family roots (scaffold 6-8) ---------------------------- +-define(NREF_NAMES, 6). +-define(NREF_LITERALS, 7). +-define(NREF_RELATIONSHIPS, 8). + +%% -- Name-attribute subcategory nodes (scaffold 9-12) ----------------- +-define(NREF_CAT_NAME_ATTRS, 9). +-define(NREF_ATTR_NAME_ATTRS, 10). +-define(NREF_CLS_NAME_ATTRS, 11). +-define(NREF_INST_NAME_ATTRS, 12). + +%% -- Relationship-attribute subcategory nodes (scaffold 13-16) -------- +-define(NREF_CAT_REL_ATTRS, 13). +-define(NREF_ATTR_REL_ATTRS, 14). +-define(NREF_CLS_REL_ATTRS, 15). +-define(NREF_INST_REL_ATTRS, 16). + +%% -- Name attributes: used as #{attribute => ?NAME_ATTR_*, value => Name} +-define(NAME_ATTR_CATEGORY, 17). +-define(NAME_ATTR_ATTRIBUTE, 18). +-define(NAME_ATTR_CLASS, 19). +-define(NAME_ATTR_INSTANCE, 20). + +%% -- Category hierarchy arc labels (kind = composition) --------------- +-define(ARC_CAT_PARENT, 21). +-define(ARC_CAT_CHILD, 22). + +%% -- Attribute hierarchy arc labels (kind = taxonomy) ----------------- +-define(ARC_ATTR_PARENT, 23). +-define(ARC_ATTR_CHILD, 24). + +%% -- Class hierarchy arc labels (kind = taxonomy or composition) ------ +-define(ARC_CLS_PARENT, 25). +-define(ARC_CLS_CHILD, 26). + +%% -- Instance hierarchy arc labels (kind = composition) --------------- +-define(ARC_INST_PARENT, 27). +-define(ARC_INST_CHILD, 28). + +%% -- Instance-class membership arc labels ----------------------------- +-define(ARC_INST_TO_CLASS, 29). %% instance -> class direction +-define(ARC_CLASS_TO_INST, 30). %% class -> instance direction + +%% -- Template scope AVP marker ---------------------------------------- +-define(ARC_TEMPLATE, 31). + +%% -- Language subcategories (scaffold 32-35) -------------------------- +-define(NREF_HUMAN_LANGS, 32). +-define(NREF_FORMAL_LANGS, 33). +-define(NREF_DIAGRAM_LANGS, 34). +-define(NREF_RENDERERS, 35). + +%% -- Permanent named instance nrefs (10000-99999 tier) ---------------- +-define(NREF_ENGLISH, 10000). %% English; first instance in ontology diff --git a/apps/graphdb/src/graphdb_bootstrap.erl b/apps/graphdb/src/graphdb_bootstrap.erl index 5193cc7..0162279 100644 --- a/apps/graphdb/src/graphdb_bootstrap.erl +++ b/apps/graphdb/src/graphdb_bootstrap.erl @@ -56,6 +56,11 @@ end)). +%%--------------------------------------------------------------------- +%% Include Files +%%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). + %%--------------------------------------------------------------------- %% Record Definitions %%--------------------------------------------------------------------- From 3241de558be58fe26c9eb5ec247a48b9ac5a1a5d Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 05:39:40 -0400 Subject: [PATCH 2/8] Task 2: add graphdb_nrefs.erl + graphdb_nrefs_SUITE.erl scaffold_spec/0 enumerates all 36 scaffold+permanent nrefs as {MacroName, Nref, Kind, ExpectedName} tuples. verify/0 reads each from Mnesia and confirms kind + name AVP match. CT suite: verify_returns_ok passes; bootstrap_module_unloaded fails (expected -- unloading not yet implemented, pending Task 4). Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/src/graphdb_nrefs.erl | 111 +++++++++++++++++ apps/graphdb/test/graphdb_nrefs_SUITE.erl | 140 ++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 apps/graphdb/src/graphdb_nrefs.erl create mode 100644 apps/graphdb/test/graphdb_nrefs_SUITE.erl diff --git a/apps/graphdb/src/graphdb_nrefs.erl b/apps/graphdb/src/graphdb_nrefs.erl new file mode 100644 index 0000000..8170dd9 --- /dev/null +++ b/apps/graphdb/src/graphdb_nrefs.erl @@ -0,0 +1,111 @@ +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +%% Author: David W. Thomas +%% Created: 2026-05-20 +%% Description: Runtime catalog of scaffold nrefs and bootstrap +%% congruency verification. +%%--------------------------------------------------------------------- +%% Revision History +%%--------------------------------------------------------------------- +%% Rev PA1 Date: 2026-05-20 Author: David W. Thomas +%% Initial implementation. +%%--------------------------------------------------------------------- + +-module(graphdb_nrefs). + +-include_lib("graphdb/include/graphdb_nrefs.hrl"). + +-record(node, { + nref, + kind, + parents = [], + classes = [], + attribute_value_pairs +}). + +-export([scaffold_spec/0, verify/0]). + + +%%--------------------------------------------------------------------- +%% scaffold_spec() -> [{atom(), integer(), atom(), string()}] +%% +%% Returns {MacroName, Nref, Kind, ExpectedName} for every immutable +%% scaffold nref. Used by verify/0 and CT tests. +%%--------------------------------------------------------------------- +scaffold_spec() -> [ + {nref_root, ?NREF_ROOT, category, "Root"}, + {nref_attributes, ?NREF_ATTRIBUTES, category, "Attributes"}, + {nref_classes, ?NREF_CLASSES, category, "Classes"}, + {nref_languages, ?NREF_LANGUAGES, category, "Languages"}, + {nref_projects, ?NREF_PROJECTS, category, "Projects"}, + {nref_names, ?NREF_NAMES, attribute, "Names"}, + {nref_literals, ?NREF_LITERALS, attribute, "Literals"}, + {nref_relationships, ?NREF_RELATIONSHIPS, attribute, "Relationships"}, + {nref_cat_name_attrs, ?NREF_CAT_NAME_ATTRS, attribute, "Category Name Attributes"}, + {nref_attr_name_attrs,?NREF_ATTR_NAME_ATTRS, attribute, "Attribute Name Attributes"}, + {nref_cls_name_attrs, ?NREF_CLS_NAME_ATTRS, attribute, "Class Name Attributes"}, + {nref_inst_name_attrs,?NREF_INST_NAME_ATTRS, attribute, "Instance Name Attributes"}, + {nref_cat_rel_attrs, ?NREF_CAT_REL_ATTRS, attribute, "Category Relationships"}, + {nref_attr_rel_attrs, ?NREF_ATTR_REL_ATTRS, attribute, "Attribute Relationships"}, + {nref_cls_rel_attrs, ?NREF_CLS_REL_ATTRS, attribute, "Class Relationships"}, + {nref_inst_rel_attrs, ?NREF_INST_REL_ATTRS, attribute, "Instance Relationships"}, + {name_attr_category, ?NAME_ATTR_CATEGORY, attribute, "Name"}, + {name_attr_attribute, ?NAME_ATTR_ATTRIBUTE, attribute, "Name"}, + {name_attr_class, ?NAME_ATTR_CLASS, attribute, "Name"}, + {name_attr_instance, ?NAME_ATTR_INSTANCE, attribute, "Name"}, + {arc_cat_parent, ?ARC_CAT_PARENT, attribute, "Parent"}, + {arc_cat_child, ?ARC_CAT_CHILD, attribute, "Child"}, + {arc_attr_parent, ?ARC_ATTR_PARENT, attribute, "Parent"}, + {arc_attr_child, ?ARC_ATTR_CHILD, attribute, "Child"}, + {arc_cls_parent, ?ARC_CLS_PARENT, attribute, "Parent"}, + {arc_cls_child, ?ARC_CLS_CHILD, attribute, "Child"}, + {arc_inst_parent, ?ARC_INST_PARENT, attribute, "Parent"}, + {arc_inst_child, ?ARC_INST_CHILD, attribute, "Child"}, + {arc_inst_to_class, ?ARC_INST_TO_CLASS, attribute, "Class"}, + {arc_class_to_inst, ?ARC_CLASS_TO_INST, attribute, "Instance"}, + {arc_template, ?ARC_TEMPLATE, attribute, "Template"}, + {nref_human_langs, ?NREF_HUMAN_LANGS, category, "Human Languages"}, + {nref_formal_langs, ?NREF_FORMAL_LANGS, category, "Formal Languages"}, + {nref_diagram_langs, ?NREF_DIAGRAM_LANGS, category, "Diagram Languages"}, + {nref_renderers, ?NREF_RENDERERS, category, "Renderers"}, + {nref_english, ?NREF_ENGLISH, instance, "English"} +]. + + +%%--------------------------------------------------------------------- +%% verify() -> ok | {error, {scaffold_nref_mismatch, [term()]}} +%% +%% Reads every scaffold nref from Mnesia and confirms it has the +%% expected kind and name AVP. Called at the end of bootstrap load. +%%--------------------------------------------------------------------- +verify() -> + Mismatches = lists:flatmap(fun verify_one/1, scaffold_spec()), + case Mismatches of + [] -> ok; + _ -> {error, {scaffold_nref_mismatch, Mismatches}} + end. + +verify_one({Name, Nref, ExpKind, ExpNameValue}) -> + NameAttr = name_attr_for_kind(ExpKind), + case mnesia:dirty_read(nodes, Nref) of + [#node{kind = ExpKind, attribute_value_pairs = AVPs}] -> + HasName = lists:any( + fun(#{attribute := A, value := V}) -> + A =:= NameAttr andalso V =:= ExpNameValue + end, AVPs), + case HasName of + true -> []; + false -> [{Name, Nref, name_not_found, ExpNameValue}] + end; + [#node{kind = ActualKind}] -> + [{Name, Nref, kind_mismatch, ExpKind, ActualKind}]; + [] -> + [{Name, Nref, node_not_found}] + end. + +name_attr_for_kind(category) -> ?NAME_ATTR_CATEGORY; +name_attr_for_kind(attribute) -> ?NAME_ATTR_ATTRIBUTE; +name_attr_for_kind(class) -> ?NAME_ATTR_CLASS; +name_attr_for_kind(instance) -> ?NAME_ATTR_INSTANCE. diff --git a/apps/graphdb/test/graphdb_nrefs_SUITE.erl b/apps/graphdb/test/graphdb_nrefs_SUITE.erl new file mode 100644 index 0000000..56230a7 --- /dev/null +++ b/apps/graphdb/test/graphdb_nrefs_SUITE.erl @@ -0,0 +1,140 @@ +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +%% Author: David W. Thomas +%% Created: 2026-05-20 +%% Description: Common Test suite for graphdb_nrefs congruency +%% verification and bootstrap module lifecycle. +%%--------------------------------------------------------------------- +-module(graphdb_nrefs_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). + +-define(SCRATCH_SENTINEL, "_build/test/ct_scratch/"). +-define(DIR_PREFIX, "nrefs_"). + +%%--------------------------------------------------------------------- +%% Common Test callbacks +%%--------------------------------------------------------------------- +-export([all/0, groups/0, suite/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). + +%%--------------------------------------------------------------------- +%% Test cases +%%--------------------------------------------------------------------- +-export([verify_returns_ok/1, + bootstrap_module_unloaded/1]). + +suite() -> [{timetrap, {seconds, 30}}]. + +all() -> [{group, congruency}]. + +groups() -> + [{congruency, [sequence], [ + verify_returns_ok, + bootstrap_module_unloaded + ]}]. + + +%%--------------------------------------------------------------------- +%% Suite setup +%%--------------------------------------------------------------------- +init_per_suite(Config) -> + {ok, OrigCwd} = file:get_cwd(), + ok = case application:load(graphdb) of + ok -> ok; + {error, {already_loaded, graphdb}} -> ok + end, + PrivDir = code:priv_dir(graphdb), + BootstrapFile = filename:join(PrivDir, "bootstrap.terms"), + true = filelib:is_file(BootstrapFile), + [{orig_cwd, OrigCwd}, {bootstrap_file, BootstrapFile} | Config]. + +end_per_suite(_Config) -> ok. + + +%%--------------------------------------------------------------------- +%% Per-testcase setup/teardown +%%--------------------------------------------------------------------- +init_per_testcase(_TC, Config) -> + OrigCwd = proplists:get_value(orig_cwd, Config), + Unique = integer_to_list(erlang:unique_integer([positive, monotonic])), + TmpDir = filename:join([OrigCwd, "_build", "test", "ct_scratch", + ?DIR_PREFIX ++ Unique]), + MnesiaDir = filename:join(TmpDir, "mnesia"), + ok = filelib:ensure_dir(filename:join(MnesiaDir, "x")), + ok = file:set_cwd(TmpDir), + application:set_env(mnesia, dir, MnesiaDir), + BootstrapFile = proplists:get_value(bootstrap_file, Config), + application:set_env(seerstone_graph_db, bootstrap_file, BootstrapFile), + {ok, _} = application:ensure_all_started(nref), + {ok, _} = rel_id_server:start_link(), + {ok, _} = graphdb_mgr:start_link(), + [{tmp_dir, TmpDir} | Config]. + +end_per_testcase(TC, Config) -> + verify_cache_invariant(TC), + catch gen_server:stop(graphdb_mgr), + catch gen_server:stop(rel_id_server), + catch application:stop(nref), + catch mnesia:stop(), + catch dets:close(nref_server), + catch dets:close(nref_allocator), + catch dets:close(rel_id_server), + OrigCwd = proplists:get_value(orig_cwd, Config), + ok = file:set_cwd(OrigCwd), + TmpDir = proplists:get_value(tmp_dir, Config), + delete_dir_recursive(TmpDir), + application:unset_env(seerstone_graph_db, bootstrap_file), + application:unset_env(mnesia, dir), + ok. + + +%%===================================================================== +%% Test Cases +%%===================================================================== + +%%--------------------------------------------------------------------- +%% After a successful bootstrap, every macro value must match its +%% corresponding Mnesia node (kind + name AVP). +%%--------------------------------------------------------------------- +verify_returns_ok(_Config) -> + ?assertEqual(ok, graphdb_nrefs:verify()). + +%%--------------------------------------------------------------------- +%% graphdb_mgr:init/1 unloads graphdb_bootstrap from the code server +%% after a successful load. code:is_loaded/1 returns false when a +%% module is absent from the code server. +%%--------------------------------------------------------------------- +bootstrap_module_unloaded(_Config) -> + ?assertEqual(false, code:is_loaded(graphdb_bootstrap)). + + +%%===================================================================== +%% Helpers +%%===================================================================== + +verify_cache_invariant(TC) -> + case mnesia:system_info(is_running) of + yes -> + case graphdb_mgr:verify_caches() of + ok -> ok; + {error, Mismatches} -> + ct:pal("Cache invariant failed in ~p:~n~p", [TC, Mismatches]), + ct:fail({cache_invariant_failed, TC, Mismatches}) + end; + _ -> ok + end. + +delete_dir_recursive(Dir) -> + IsAbsolute = filename:pathtype(Dir) =:= absolute, + HasSentinel = string:find(Dir, ?SCRATCH_SENTINEL) =/= nomatch, + HasPrefix = lists:prefix(?DIR_PREFIX, filename:basename(Dir)), + case IsAbsolute andalso HasSentinel andalso HasPrefix of + true -> os:cmd("rm -rf \"" ++ Dir ++ "\""), ok; + false -> ct:fail({unsafe_delete, Dir}) + end. From ab4fc7ac567c3af812179755dc157f296a3e6340 Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 05:40:21 -0400 Subject: [PATCH 3/8] Task 3: call graphdb_nrefs:verify/0 at end of bootstrap do_load After rebuild_and_verify_caches() succeeds, verify/0 confirms every scaffold nref has the expected kind and name AVP in Mnesia. A mismatch is a fatal startup error (throws {error, ...}). Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/src/graphdb_bootstrap.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/graphdb/src/graphdb_bootstrap.erl b/apps/graphdb/src/graphdb_bootstrap.erl index 0162279..f5b27d2 100644 --- a/apps/graphdb/src/graphdb_bootstrap.erl +++ b/apps/graphdb/src/graphdb_bootstrap.erl @@ -245,6 +245,10 @@ do_load() -> ok = write_nodes(ResNodes), ok = write_relationships(ResRels), ok = rebuild_and_verify_caches(), + ok = case graphdb_nrefs:verify() of + ok -> ok; + {error, _} = E -> throw(E) + end, logger:info("graphdb_bootstrap: loaded ~p nodes, ~p relationship pairs", [length(ResNodes), length(ResRels)]), ok; From 29f24366f6e67823d54d7c282eeb79c3026f217b Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 05:40:58 -0400 Subject: [PATCH 4/8] Task 4: unload graphdb_bootstrap after successful bootstrap code:delete/1 + code:purge/1 in graphdb_mgr:init/1 removes the module from the code server immediately after a successful load. Erlang reloads it from the code path on demand (e.g. CT suite restarts). Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/src/graphdb_mgr.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/graphdb/src/graphdb_mgr.erl b/apps/graphdb/src/graphdb_mgr.erl index d26e43f..3490a22 100644 --- a/apps/graphdb/src/graphdb_mgr.erl +++ b/apps/graphdb/src/graphdb_mgr.erl @@ -309,6 +309,8 @@ rebuild_caches() -> init([]) -> case graphdb_bootstrap:load() of ok -> + code:delete(graphdb_bootstrap), + code:purge(graphdb_bootstrap), logger:info("graphdb_mgr: started, bootstrap loaded"), {ok, #state{}}; {error, Reason} -> From c3337889e97dcc3ec331ffa0c3da914eebe6dede Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 05:46:58 -0400 Subject: [PATCH 5/8] Task 5: migrate source files to shared graphdb_nrefs.hrl macros Replace inline scaffold-nref -define blocks in five source files with -include_lib("graphdb/include/graphdb_nrefs.hrl") and rename all usages to the canonical macro names (NREF_*, NAME_ATTR_*, ARC_*). graphdb_attr : PARENT_NAMES/LITERALS/RELATIONSHIPS, NAME_ATTR_FOR_ATTRIBUTE, ATTR_CHILD/PARENT_ARC, TEMPLATE_AVP_NREF graphdb_class : CLASSES_CATEGORY, NAME_ATTR_FOR_CLASS, CLASS_CHILD/PARENT_ARC graphdb_instance: NAME_ATTR_FOR_INSTANCE, INST_CHILD/PARENT_ARC, CLASS/INSTANCE_MEMBERSHIP_ARC, TEMPLATE_AVP_NREF graphdb_language: PARENT_LITERALS/CLASSES, HUMAN_LANGS, NAME_ATTR_FOR_*, ATTR/CLASS/INST arc macros, CLASS/INSTANCE_MEMBERSHIP_ARC graphdb_mgr : PARENT_ARCS updated to use arc macros; CLASS_MEMBERSHIP_ARC Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/src/graphdb_attr.erl | 94 ++++++++++----------------- apps/graphdb/src/graphdb_class.erl | 73 +++++++++------------ apps/graphdb/src/graphdb_instance.erl | 53 +++++---------- apps/graphdb/src/graphdb_language.erl | 65 +++++++----------- apps/graphdb/src/graphdb_mgr.erl | 6 +- 5 files changed, 110 insertions(+), 181 deletions(-) diff --git a/apps/graphdb/src/graphdb_attr.erl b/apps/graphdb/src/graphdb_attr.erl index e9958e5..f77792c 100644 --- a/apps/graphdb/src/graphdb_attr.erl +++ b/apps/graphdb/src/graphdb_attr.erl @@ -57,6 +57,7 @@ %%--------------------------------------------------------------------- %% Include files %%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- %% Macro Functions @@ -78,29 +79,6 @@ end)). -%%--------------------------------------------------------------------- -%% Bootstrap nref constants -%%--------------------------------------------------------------------- -%% Parents in the attribute library tree. --define(PARENT_NAMES, 6). %% Names subtree --define(PARENT_LITERALS, 7). %% Literals subtree --define(PARENT_RELATIONSHIPS, 8). %% Relationships subtree - -%% NameAttrNref for attribute-kind nodes (self-ref in bootstrap). --define(NAME_ATTR_FOR_ATTRIBUTE, 18). - -%% Compositional arc labels for attribute children of attribute parents -%% (self-referential arc labels 23 and 24 from bootstrap). --define(ATTR_CHILD_ARC, 24). %% Child/AttrRel -- parent -> child --define(ATTR_PARENT_ARC, 23). %% Parent/AttrRel -- child -> parent - -%% Bootstrap-seeded `Template` relationship-AVP marker attribute. The -%% `relationship_avp => true` flag is stamped on this node by init/1 -%% post-bootstrap (the flag-attribute is seeded first, then this node -%% is updated -- bootstrap.terms cannot reference a runtime-seeded nref). --define(TEMPLATE_AVP_NREF, 31). - - %%--------------------------------------------------------------------- %% Record Definitions %%--------------------------------------------------------------------- @@ -338,14 +316,14 @@ init([]) -> %%----------------------------------------------------------------------------- handle_call({create_name_attribute, Name}, _From, State) -> Extra = [attr_type_avp(name, State)], - Reply = do_create_attribute(Name, ?PARENT_NAMES, Extra), + Reply = do_create_attribute(Name, ?NREF_NAMES, Extra), {reply, Reply, State}; handle_call({create_literal_attribute, Name, Type}, _From, #state{literal_type_nref = TypeAttr} = State) -> Extra = [#{attribute => TypeAttr, value => Type}, attr_type_avp(literal, State)], - Reply = do_create_attribute(Name, ?PARENT_LITERALS, Extra), + Reply = do_create_attribute(Name, ?NREF_LITERALS, Extra), {reply, Reply, State}; handle_call({create_relationship_attribute, Name, ReciprocalName, TargetKind}, @@ -358,7 +336,7 @@ handle_call({create_relationship_attribute, Name, ReciprocalName, TargetKind}, handle_call({create_relationship_type, Name}, _From, State) -> Extra = [attr_type_avp(relationship, State)], - Reply = do_create_attribute(Name, ?PARENT_RELATIONSHIPS, Extra), + Reply = do_create_attribute(Name, ?NREF_RELATIONSHIPS, Extra), {reply, Reply, State}; %%----------------------------------------------------------------------------- @@ -371,7 +349,7 @@ handle_call(list_attributes, _From, State) -> {reply, do_list_attributes(), State}; handle_call(list_relationship_types, _From, State) -> - {reply, do_list_children(?PARENT_RELATIONSHIPS), State}; + {reply, do_list_children(?NREF_RELATIONSHIPS), State}; handle_call({attribute_type_of, Nref}, _From, #state{attribute_type_nref = AtAttr} = State) -> @@ -428,11 +406,11 @@ valid_target_kind(_) -> false. %% Throws {error, Reason} on failure. %%----------------------------------------------------------------------------- ensure_seed(Name) -> - case find_attribute_by_name(?PARENT_LITERALS, Name) of + case find_attribute_by_name(?NREF_LITERALS, Name) of {ok, Nref} -> Nref; not_found -> - case do_create_attribute(Name, ?PARENT_LITERALS, []) of + case do_create_attribute(Name, ?NREF_LITERALS, []) of {ok, Nref} -> Nref; {error, Reason} -> throw({error, Reason}) end @@ -448,7 +426,7 @@ ensure_seed(Name) -> %%----------------------------------------------------------------------------- find_attribute_by_name(ParentNref, Name) -> F = fun() -> - Children = downward_children_by_arc(ParentNref, ?ATTR_CHILD_ARC, + Children = downward_children_by_arc(ParentNref, ?ARC_ATTR_CHILD, taxonomy), lists:search(fun(N) -> node_has_name(N, Name) end, Children) end, @@ -464,7 +442,7 @@ find_attribute_by_name(ParentNref, Name) -> %%----------------------------------------------------------------------------- node_has_name(#node{attribute_value_pairs = AVPs}, Name) -> lists:any(fun - (#{attribute := ?NAME_ATTR_FOR_ATTRIBUTE, value := V}) -> V =:= Name; + (#{attribute := ?NAME_ATTR_ATTRIBUTE, value := V}) -> V =:= Name; (_) -> false end, AVPs). @@ -484,7 +462,7 @@ node_has_name(#node{attribute_value_pairs = AVPs}, Name) -> %%----------------------------------------------------------------------------- do_create_attribute(Name, ParentNref, ExtraAVPs) -> Nref = nref_server:get_nref(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_ATTRIBUTE, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_ATTRIBUTE, value => Name}, Node = #node{ nref = Nref, kind = attribute, @@ -496,18 +474,18 @@ do_create_attribute(Name, ParentNref, ExtraAVPs) -> id = Id1, kind = taxonomy, source_nref = ParentNref, - characterization = ?ATTR_CHILD_ARC, + characterization = ?ARC_ATTR_CHILD, target_nref = Nref, - reciprocal = ?ATTR_PARENT_ARC, + reciprocal = ?ARC_ATTR_PARENT, avps = [] }, C2P = #relationship{ id = Id2, kind = taxonomy, source_nref = Nref, - characterization = ?ATTR_PARENT_ARC, + characterization = ?ARC_ATTR_PARENT, target_nref = ParentNref, - reciprocal = ?ATTR_CHILD_ARC, + reciprocal = ?ARC_ATTR_CHILD, avps = [] }, Txn = fun() -> @@ -536,58 +514,58 @@ do_create_relationship_attribute_pair(FwdName, RevName, ExtraAVPs) -> RevNref = nref_server:get_nref(), {Id1, Id2} = rel_id_server:get_id_pair(), {Id3, Id4} = rel_id_server:get_id_pair(), - FwdAVPs = [#{attribute => ?NAME_ATTR_FOR_ATTRIBUTE, value => FwdName} + FwdAVPs = [#{attribute => ?NAME_ATTR_ATTRIBUTE, value => FwdName} | ExtraAVPs], - RevAVPs = [#{attribute => ?NAME_ATTR_FOR_ATTRIBUTE, value => RevName} + RevAVPs = [#{attribute => ?NAME_ATTR_ATTRIBUTE, value => RevName} | ExtraAVPs], FwdNode = #node{ nref = FwdNref, kind = attribute, - parents = [?PARENT_RELATIONSHIPS], + parents = [?NREF_RELATIONSHIPS], attribute_value_pairs = FwdAVPs }, RevNode = #node{ nref = RevNref, kind = attribute, - parents = [?PARENT_RELATIONSHIPS], + parents = [?NREF_RELATIONSHIPS], attribute_value_pairs = RevAVPs }, %% Forward node taxonomy arcs. FwdP2C = #relationship{ id = Id1, kind = taxonomy, - source_nref = ?PARENT_RELATIONSHIPS, - characterization = ?ATTR_CHILD_ARC, + source_nref = ?NREF_RELATIONSHIPS, + characterization = ?ARC_ATTR_CHILD, target_nref = FwdNref, - reciprocal = ?ATTR_PARENT_ARC, + reciprocal = ?ARC_ATTR_PARENT, avps = [] }, FwdC2P = #relationship{ id = Id2, kind = taxonomy, source_nref = FwdNref, - characterization = ?ATTR_PARENT_ARC, - target_nref = ?PARENT_RELATIONSHIPS, - reciprocal = ?ATTR_CHILD_ARC, + characterization = ?ARC_ATTR_PARENT, + target_nref = ?NREF_RELATIONSHIPS, + reciprocal = ?ARC_ATTR_CHILD, avps = [] }, %% Reciprocal node taxonomy arcs. RevP2C = #relationship{ id = Id3, kind = taxonomy, - source_nref = ?PARENT_RELATIONSHIPS, - characterization = ?ATTR_CHILD_ARC, + source_nref = ?NREF_RELATIONSHIPS, + characterization = ?ARC_ATTR_CHILD, target_nref = RevNref, - reciprocal = ?ATTR_PARENT_ARC, + reciprocal = ?ARC_ATTR_PARENT, avps = [] }, RevC2P = #relationship{ id = Id4, kind = taxonomy, source_nref = RevNref, - characterization = ?ATTR_PARENT_ARC, - target_nref = ?PARENT_RELATIONSHIPS, - reciprocal = ?ATTR_CHILD_ARC, + characterization = ?ARC_ATTR_PARENT, + target_nref = ?NREF_RELATIONSHIPS, + reciprocal = ?ARC_ATTR_CHILD, avps = [] }, Txn = fun() -> @@ -635,7 +613,7 @@ do_list_attributes() -> %%----------------------------------------------------------------------------- do_list_children(ParentNref) -> F = fun() -> - downward_children_by_arc(ParentNref, ?ATTR_CHILD_ARC, taxonomy) + downward_children_by_arc(ParentNref, ?ARC_ATTR_CHILD, taxonomy) end, case mnesia:transaction(F) of {atomic, Nodes} -> {ok, Nodes}; @@ -758,9 +736,9 @@ has_attribute_type_avp(AVPs, AtAttrNref) -> %% Walks the parents cache to determine which top-level attribute %% subtree (6/7/8) the node belongs to. Must run inside a Mnesia %% transaction. Visited list guards against cycles in malformed data. -classify_attribute_node(?PARENT_NAMES, _Visited) -> name; -classify_attribute_node(?PARENT_LITERALS, _Visited) -> literal; -classify_attribute_node(?PARENT_RELATIONSHIPS, _Visited) -> relationship; +classify_attribute_node(?NREF_NAMES, _Visited) -> name; +classify_attribute_node(?NREF_LITERALS, _Visited) -> literal; +classify_attribute_node(?NREF_RELATIONSHIPS, _Visited) -> relationship; classify_attribute_node(Nref, Visited) -> case lists:member(Nref, Visited) of true -> @@ -787,7 +765,7 @@ classify_attribute_node(Nref, Visited) -> %%----------------------------------------------------------------------------- ensure_template_avp_marker(RelAvpAttrNref) -> Txn = fun() -> - case mnesia:read(nodes, ?TEMPLATE_AVP_NREF) of + case mnesia:read(nodes, ?ARC_TEMPLATE) of [#node{attribute_value_pairs = AVPs} = Node] -> Already = lists:any(fun (#{attribute := A, value := true}) -> A =:= RelAvpAttrNref; @@ -804,7 +782,7 @@ ensure_template_avp_marker(RelAvpAttrNref) -> ok = mnesia:write(nodes, Updated, write) end; [] -> - throw({error, {template_avp_node_missing, ?TEMPLATE_AVP_NREF}}) + throw({error, {template_avp_node_missing, ?ARC_TEMPLATE}}) end end, case mnesia:transaction(Txn) of diff --git a/apps/graphdb/src/graphdb_class.erl b/apps/graphdb/src/graphdb_class.erl index e28b112..caa6de4 100644 --- a/apps/graphdb/src/graphdb_class.erl +++ b/apps/graphdb/src/graphdb_class.erl @@ -47,6 +47,7 @@ %%--------------------------------------------------------------------- %% Include files %%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- %% Macro Functions @@ -68,22 +69,6 @@ end)). -%%--------------------------------------------------------------------- -%% Bootstrap nref constants -%%--------------------------------------------------------------------- -%% Classes category — top-level organisational anchor for all classes. --define(CLASSES_CATEGORY, 3). - -%% NameAttrNref for class-kind nodes. --define(NAME_ATTR_FOR_CLASS, 19). - -%% Compositional arc labels for class children of class parents. --define(CLASS_CHILD_ARC, 26). %% Child/ClassRel -- parent -> child --define(CLASS_PARENT_ARC, 25). %% Parent/ClassRel -- child -> parent - -%% Default template name auto-attached to every newly created class. -%% Class authors may delete the default template to force explicit -%% disambiguation on subsequent Connection arcs. -define(DEFAULT_TEMPLATE_NAME, "default"). @@ -423,8 +408,8 @@ do_create_class(Name, ParentClassNref) -> {TaxId1, TaxId2} = rel_id_server:get_id_pair(), TemplateNref = nref_server:get_nref(), {TmplCompId1, TmplCompId2} = rel_id_server:get_id_pair(), - ClassNameAVP = #{attribute => ?NAME_ATTR_FOR_CLASS, value => Name}, - TemplateNameAVP = #{attribute => ?NAME_ATTR_FOR_CLASS, + ClassNameAVP = #{attribute => ?NAME_ATTR_CLASS, value => Name}, + TemplateNameAVP = #{attribute => ?NAME_ATTR_CLASS, value => ?DEFAULT_TEMPLATE_NAME}, ClassNode = #node{ nref = ClassNref, @@ -441,33 +426,33 @@ do_create_class(Name, ParentClassNref) -> TaxP2C = #relationship{ id = TaxId1, kind = taxonomy, source_nref = ParentClassNref, - characterization = ?CLASS_CHILD_ARC, + characterization = ?ARC_CLS_CHILD, target_nref = ClassNref, - reciprocal = ?CLASS_PARENT_ARC, + reciprocal = ?ARC_CLS_PARENT, avps = [] }, TaxC2P = #relationship{ id = TaxId2, kind = taxonomy, source_nref = ClassNref, - characterization = ?CLASS_PARENT_ARC, + characterization = ?ARC_CLS_PARENT, target_nref = ParentClassNref, - reciprocal = ?CLASS_CHILD_ARC, + reciprocal = ?ARC_CLS_CHILD, avps = [] }, TmplP2C = #relationship{ id = TmplCompId1, kind = composition, source_nref = ClassNref, - characterization = ?CLASS_CHILD_ARC, + characterization = ?ARC_CLS_CHILD, target_nref = TemplateNref, - reciprocal = ?CLASS_PARENT_ARC, + reciprocal = ?ARC_CLS_PARENT, avps = [] }, TmplC2P = #relationship{ id = TmplCompId2, kind = composition, source_nref = TemplateNref, - characterization = ?CLASS_PARENT_ARC, + characterization = ?ARC_CLS_PARENT, target_nref = ClassNref, - reciprocal = ?CLASS_CHILD_ARC, + reciprocal = ?ARC_CLS_CHILD, avps = [] }, Txn = fun() -> @@ -522,17 +507,17 @@ do_write_superclass(ClassNref, AdditionalParentNref) -> C2P = #relationship{ id = Id1, kind = taxonomy, source_nref = ClassNref, - characterization = ?CLASS_PARENT_ARC, + characterization = ?ARC_CLS_PARENT, target_nref = AdditionalParentNref, - reciprocal = ?CLASS_CHILD_ARC, + reciprocal = ?ARC_CLS_CHILD, avps = [] }, P2C = #relationship{ id = Id2, kind = taxonomy, source_nref = AdditionalParentNref, - characterization = ?CLASS_CHILD_ARC, + characterization = ?ARC_CLS_CHILD, target_nref = ClassNref, - reciprocal = ?CLASS_PARENT_ARC, + reciprocal = ?ARC_CLS_PARENT, avps = [] }, Updated = Node#node{ @@ -574,7 +559,7 @@ do_add_template(ClassNref, Name) -> do_write_template(ClassNref, Name) -> TemplateNref = nref_server:get_nref(), {Id1, Id2} = rel_id_server:get_id_pair(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_CLASS, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_CLASS, value => Name}, Node = #node{ nref = TemplateNref, kind = template, @@ -584,17 +569,17 @@ do_write_template(ClassNref, Name) -> P2C = #relationship{ id = Id1, kind = composition, source_nref = ClassNref, - characterization = ?CLASS_CHILD_ARC, + characterization = ?ARC_CLS_CHILD, target_nref = TemplateNref, - reciprocal = ?CLASS_PARENT_ARC, + reciprocal = ?ARC_CLS_PARENT, avps = [] }, C2P = #relationship{ id = Id2, kind = composition, source_nref = TemplateNref, - characterization = ?CLASS_PARENT_ARC, + characterization = ?ARC_CLS_PARENT, target_nref = ClassNref, - reciprocal = ?CLASS_CHILD_ARC, + reciprocal = ?ARC_CLS_CHILD, avps = [] }, Txn = fun() -> @@ -617,7 +602,7 @@ do_write_template(ClassNref, Name) -> %%----------------------------------------------------------------------------- do_find_template_by_name(ClassNref, Name) -> F = fun() -> - Children = downward_children_by_arc(ClassNref, ?CLASS_CHILD_ARC, + Children = downward_children_by_arc(ClassNref, ?ARC_CLS_CHILD, composition), lists:search(fun (#node{kind = template} = N) -> template_has_name(N, Name); @@ -632,7 +617,7 @@ do_find_template_by_name(ClassNref, Name) -> template_has_name(#node{attribute_value_pairs = AVPs}, Name) -> lists:any(fun - (#{attribute := ?NAME_ATTR_FOR_CLASS, value := V}) -> V =:= Name; + (#{attribute := ?NAME_ATTR_CLASS, value := V}) -> V =:= Name; (_) -> false end, AVPs). @@ -654,7 +639,7 @@ do_get_template(Nref) -> %%----------------------------------------------------------------------------- do_templates_for_class(ClassNref) -> F = fun() -> - Children = downward_children_by_arc(ClassNref, ?CLASS_CHILD_ARC, + Children = downward_children_by_arc(ClassNref, ?ARC_CLS_CHILD, composition), [N || N <- Children, N#node.kind =:= template] end, @@ -698,7 +683,7 @@ do_class_in_ancestry(CandidateNref, ClassNref) -> %% Validates that ParentNref is either the Classes category (nref 3) or %% an existing class node. %%----------------------------------------------------------------------------- -do_validate_parent(?CLASSES_CATEGORY) -> +do_validate_parent(?NREF_CLASSES) -> ok; do_validate_parent(Nref) -> case mnesia:dirty_read(nodes, Nref) of @@ -779,7 +764,7 @@ do_get_class(Nref) -> %%----------------------------------------------------------------------------- do_subclasses(ClassNref) -> F = fun() -> - Children = downward_children_by_arc(ClassNref, ?CLASS_CHILD_ARC, + Children = downward_children_by_arc(ClassNref, ?ARC_CLS_CHILD, taxonomy), [N || N <- Children, N#node.kind =:= class] end, @@ -803,7 +788,7 @@ do_subclasses(ClassNref) -> do_ancestors(ClassNref) -> case mnesia:dirty_read(nodes, ClassNref) of [#node{kind = class, parents = Parents}] -> - Initial = [P || P <- Parents, P =/= ?CLASSES_CATEGORY], + Initial = [P || P <- Parents, P =/= ?NREF_CLASSES], do_walk_ancestors(Initial, sets:from_list(Initial), []); [_] -> {error, not_a_class}; @@ -821,7 +806,7 @@ do_walk_ancestors([Nref | Rest], Visited, Acc) -> case mnesia:dirty_read(nodes, Nref) of [#node{kind = class, parents = Parents} = Node] -> New = [P || P <- Parents, - P =/= ?CLASSES_CATEGORY, + P =/= ?NREF_CLASSES, not sets:is_element(P, Visited)], NewVisited = lists:foldl(fun sets:add_element/2, Visited, New), do_walk_ancestors(Rest ++ New, NewVisited, [Node | Acc]); @@ -876,14 +861,14 @@ do_inherited_qcs(ClassNref) -> %% collect_qc_avps(Nodes) -> [{integer(), term() | undefined}] %% %% Collects qualifying-characteristic {AttrNref, Value} pairs from a -%% list of nodes. The class name AVP (attribute = ?NAME_ATTR_FOR_CLASS) +%% list of nodes. The class name AVP (attribute = ?NAME_ATTR_CLASS) %% is excluded — it is not a QC. Deduplicates by AttrNref in list order %% (first occurrence wins). %%----------------------------------------------------------------------------- collect_qc_avps(Nodes) -> lists:foldl(fun(#node{attribute_value_pairs = AVPs}, Acc) -> lists:foldl(fun - (#{attribute := ?NAME_ATTR_FOR_CLASS}, A) -> + (#{attribute := ?NAME_ATTR_CLASS}, A) -> A; % skip class name AVP (#{attribute := Attr, value := V}, A) -> case lists:keymember(Attr, 1, A) of diff --git a/apps/graphdb/src/graphdb_instance.erl b/apps/graphdb/src/graphdb_instance.erl index 81988d9..4478ea0 100644 --- a/apps/graphdb/src/graphdb_instance.erl +++ b/apps/graphdb/src/graphdb_instance.erl @@ -50,6 +50,7 @@ %%--------------------------------------------------------------------- %% Include files %%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- %% Macro Functions @@ -71,26 +72,6 @@ end)). -%%--------------------------------------------------------------------- -%% Bootstrap nref constants -%%--------------------------------------------------------------------- -%% NameAttrNref for instance-kind nodes. --define(NAME_ATTR_FOR_INSTANCE, 20). - -%% Compositional arc labels for instance children of instance parents. --define(INST_CHILD_ARC, 28). %% Child/InstRel -- parent -> child --define(INST_PARENT_ARC, 27). %% Parent/InstRel -- child -> parent - -%% Instance-to-class membership arc labels. --define(CLASS_MEMBERSHIP_ARC, 29). %% instance -> class --define(INSTANCE_MEMBERSHIP_ARC, 30). %% class -> instance - -%% Bootstrap-seeded `Template` relationship-AVP marker attribute (nref 31). -%% Required on every Connection arc; its value is the nref of the template -%% node defining the semantic context for the connection. --define(TEMPLATE_AVP_NREF, 31). - - %%--------------------------------------------------------------------- %% Record Definitions %%--------------------------------------------------------------------- @@ -456,7 +437,7 @@ do_write_instance(Name, ClassNref, ParentNref) -> Nref = nref_server:get_nref(), {MembId1, MembId2} = rel_id_server:get_id_pair(), {CompId1, CompId2} = rel_id_server:get_id_pair(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_INSTANCE, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_INSTANCE, value => Name}, Node = #node{ nref = Nref, kind = instance, @@ -469,9 +450,9 @@ do_write_instance(Name, ClassNref, ParentNref) -> id = MembId1, kind = instantiation, source_nref = Nref, - characterization = ?CLASS_MEMBERSHIP_ARC, + characterization = ?ARC_INST_TO_CLASS, target_nref = ClassNref, - reciprocal = ?INSTANCE_MEMBERSHIP_ARC, + reciprocal = ?ARC_CLASS_TO_INST, avps = [] }, %% Class -> Instance (char=30, reciprocal=29) @@ -479,9 +460,9 @@ do_write_instance(Name, ClassNref, ParentNref) -> id = MembId2, kind = instantiation, source_nref = ClassNref, - characterization = ?INSTANCE_MEMBERSHIP_ARC, + characterization = ?ARC_CLASS_TO_INST, target_nref = Nref, - reciprocal = ?CLASS_MEMBERSHIP_ARC, + reciprocal = ?ARC_INST_TO_CLASS, avps = [] }, %% Parent -> Child (char=28, reciprocal=27) @@ -489,9 +470,9 @@ do_write_instance(Name, ClassNref, ParentNref) -> id = CompId1, kind = composition, source_nref = ParentNref, - characterization = ?INST_CHILD_ARC, + characterization = ?ARC_INST_CHILD, target_nref = Nref, - reciprocal = ?INST_PARENT_ARC, + reciprocal = ?ARC_INST_PARENT, avps = [] }, %% Child -> Parent (char=27, reciprocal=28) @@ -499,9 +480,9 @@ do_write_instance(Name, ClassNref, ParentNref) -> id = CompId2, kind = composition, source_nref = Nref, - characterization = ?INST_PARENT_ARC, + characterization = ?ARC_INST_PARENT, target_nref = ParentNref, - reciprocal = ?INST_CHILD_ARC, + reciprocal = ?ARC_INST_CHILD, avps = [] }, Txn = fun() -> @@ -701,7 +682,7 @@ validate_template_scope(TemplateNref, SourceClass, TargetClass) -> write_connection_arcs(SourceNref, CharNref, TargetNref, ReciprocalNref, TemplateNref, {FwdAVPs, RevAVPs}) -> {Id1, Id2} = rel_id_server:get_id_pair(), - TemplateAVP = #{attribute => ?TEMPLATE_AVP_NREF, value => TemplateNref}, + TemplateAVP = #{attribute => ?ARC_TEMPLATE, value => TemplateNref}, Fwd = #relationship{ id = Id1, kind = connection, source_nref = SourceNref, @@ -760,17 +741,17 @@ do_write_class_membership(InstanceNref, ClassNref) -> I2C = #relationship{ id = Id1, kind = instantiation, source_nref = InstanceNref, - characterization = ?CLASS_MEMBERSHIP_ARC, + characterization = ?ARC_INST_TO_CLASS, target_nref = ClassNref, - reciprocal = ?INSTANCE_MEMBERSHIP_ARC, + reciprocal = ?ARC_CLASS_TO_INST, avps = [] }, C2I = #relationship{ id = Id2, kind = instantiation, source_nref = ClassNref, - characterization = ?INSTANCE_MEMBERSHIP_ARC, + characterization = ?ARC_CLASS_TO_INST, target_nref = InstanceNref, - reciprocal = ?CLASS_MEMBERSHIP_ARC, + reciprocal = ?ARC_INST_TO_CLASS, avps = [] }, Updated = Node#node{classes = Classes ++ [ClassNref]}, @@ -811,7 +792,7 @@ do_class_of(InstanceNref) -> #relationship.source_nref), lists:search( fun(R) -> - R#relationship.characterization =:= ?CLASS_MEMBERSHIP_ARC + R#relationship.characterization =:= ?ARC_INST_TO_CLASS end, Rels) end, case mnesia:transaction(F) of @@ -841,7 +822,7 @@ do_get_instance(Nref) -> %%----------------------------------------------------------------------------- do_children(Nref) -> F = fun() -> - Children = downward_children_by_arc(Nref, ?INST_CHILD_ARC, + Children = downward_children_by_arc(Nref, ?ARC_INST_CHILD, composition), [N || N <- Children, N#node.kind =:= instance] end, diff --git a/apps/graphdb/src/graphdb_language.erl b/apps/graphdb/src/graphdb_language.erl index 72b74ac..8978fb3 100644 --- a/apps/graphdb/src/graphdb_language.erl +++ b/apps/graphdb/src/graphdb_language.erl @@ -36,6 +36,7 @@ %%--------------------------------------------------------------------- %% Include files %%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- %% Macro Functions @@ -53,22 +54,6 @@ %% Hard-coded; changing this requires a full data migration. -define(ENV_LANGUAGE_CODE, en). -%% Bootstrap nrefs --define(PARENT_LITERALS, 7). %% Literals subtree --define(PARENT_CLASSES, 3). %% Classes root --define(HUMAN_LANGS, 32). %% Human Languages category --define(NAME_ATTR_FOR_ATTRIBUTE, 18). --define(NAME_ATTR_FOR_CLASS, 19). --define(NAME_ATTR_FOR_INSTANCE, 20). --define(ATTR_PARENT_ARC, 23). %% Parent/AttrRel -- taxonomy --define(ATTR_CHILD_ARC, 24). %% Child/AttrRel -- taxonomy --define(CLASS_CHILD_ARC, 26). %% Child/ClassRel -- taxonomy --define(INST_PARENT_ARC, 27). %% Parent/InstRel --define(INST_CHILD_ARC, 28). %% Child/InstRel --define(CLASS_MEMBERSHIP_ARC, 29). --define(INSTANCE_MEMBERSHIP_ARC, 30). - - %%--------------------------------------------------------------------- %% Suppress warnings for pure helpers only used under TEST %%--------------------------------------------------------------------- @@ -234,7 +219,7 @@ do_make_chain([Code | Rest], Output, DMap) -> init([]) -> try LangCodeNref = find_literal_by_name("lang_code"), - LangHumanNref = find_class_by_name(?PARENT_CLASSES, "Human Language"), + LangHumanNref = find_class_by_name(?NREF_CLASSES, "Human Language"), BaseLangNref = ensure_literal_seed("base_language"), ProjectLangNref = ensure_literal_seed("project_language"), ok = ensure_overlay_table(language_en), @@ -380,7 +365,7 @@ code_change(_OldVsn, State, _Extra) -> %% Throws {error, Reason} if not found (bootstrap requirement). %%--------------------------------------------------------------------- find_literal_by_name(Name) -> - case graphdb_attr:find_attribute_by_name(?PARENT_LITERALS, Name) of + case graphdb_attr:find_attribute_by_name(?NREF_LITERALS, Name) of {ok, Nref} -> Nref; not_found -> throw({error, {literal_not_found, Name}}) end. @@ -395,7 +380,7 @@ find_literal_by_name(Name) -> %%--------------------------------------------------------------------- find_class_by_name(ParentNref, Name) -> F = fun() -> - Children = downward_children_by_arc(ParentNref, ?CLASS_CHILD_ARC, + Children = downward_children_by_arc(ParentNref, ?ARC_CLS_CHILD, taxonomy), lists:search(fun(N) -> class_has_name(N, Name) end, Children) end, @@ -413,35 +398,35 @@ find_class_by_name(ParentNref, Name) -> %% attribute by name under Literals (7); creates it if absent. %%--------------------------------------------------------------------- ensure_literal_seed(Name) -> - case graphdb_attr:find_attribute_by_name(?PARENT_LITERALS, Name) of + case graphdb_attr:find_attribute_by_name(?NREF_LITERALS, Name) of {ok, Nref} -> Nref; not_found -> Nref = nref_server:get_nref(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_ATTRIBUTE, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_ATTRIBUTE, value => Name}, Node = #node{ nref = Nref, kind = attribute, - parents = [?PARENT_LITERALS], + parents = [?NREF_LITERALS], attribute_value_pairs = [NameAVP] }, {Id1, Id2} = rel_id_server:get_id_pair(), P2C = #relationship{ id = Id1, kind = taxonomy, - source_nref = ?PARENT_LITERALS, - characterization = ?ATTR_CHILD_ARC, + source_nref = ?NREF_LITERALS, + characterization = ?ARC_ATTR_CHILD, target_nref = Nref, - reciprocal = ?ATTR_PARENT_ARC, + reciprocal = ?ARC_ATTR_PARENT, avps = [] }, C2P = #relationship{ id = Id2, kind = taxonomy, source_nref = Nref, - characterization = ?ATTR_PARENT_ARC, - target_nref = ?PARENT_LITERALS, - reciprocal = ?ATTR_CHILD_ARC, + characterization = ?ARC_ATTR_PARENT, + target_nref = ?NREF_LITERALS, + reciprocal = ?ARC_ATTR_CHILD, avps = [] }, F = fun() -> @@ -485,7 +470,7 @@ build_lang_maps(LangCodeNref, BaseLangNref, LangHumanNref) -> #relationship.source_nref), InstNrefs = [A#relationship.target_nref || A <- Arcs, A#relationship.kind =:= instantiation, - A#relationship.characterization =:= ?INSTANCE_MEMBERSHIP_ARC], + A#relationship.characterization =:= ?ARC_CLASS_TO_INST], Nodes = lists:flatmap(fun(N) -> mnesia:read(nodes, N) end, InstNrefs), {CM, NC} = lists:foldl(fun (#node{nref = Nref, attribute_value_pairs = AVPs}, {C, N}) -> @@ -545,7 +530,7 @@ avp_value(AttrNref, AVPs) -> %%--------------------------------------------------------------------- class_has_name(#node{attribute_value_pairs = AVPs}, Name) -> lists:any(fun - (#{attribute := ?NAME_ATTR_FOR_CLASS, value := V}) -> V =:= Name; + (#{attribute := ?NAME_ATTR_CLASS, value := V}) -> V =:= Name; (_) -> false end, AVPs). @@ -599,7 +584,7 @@ do_register_language(Code, Name, State) -> lang_human_nref = LHNref} = State, Nref = nref_server:get_nref(), {ArcId1, ArcId2} = rel_id_server:get_id_pair(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_INSTANCE, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_INSTANCE, value => Name}, CodeAVP = #{attribute => LCAttr, value => Code}, Node = #node{ nref = Nref, @@ -612,18 +597,18 @@ do_register_language(Code, Name, State) -> id = ArcId1, kind = instantiation, source_nref = Nref, - characterization = ?CLASS_MEMBERSHIP_ARC, + characterization = ?ARC_INST_TO_CLASS, target_nref = LHNref, - reciprocal = ?INSTANCE_MEMBERSHIP_ARC, + reciprocal = ?ARC_CLASS_TO_INST, avps = [] }, C2I = #relationship{ id = ArcId2, kind = instantiation, source_nref = LHNref, - characterization = ?INSTANCE_MEMBERSHIP_ARC, + characterization = ?ARC_CLASS_TO_INST, target_nref = Nref, - reciprocal = ?CLASS_MEMBERSHIP_ARC, + reciprocal = ?ARC_INST_TO_CLASS, avps = [] }, F = fun() -> @@ -661,7 +646,7 @@ do_register_dialect(Code, Name, BaseCode, State) -> lang_human_nref = LHNref} = State, Nref = nref_server:get_nref(), {ArcId1, ArcId2} = rel_id_server:get_id_pair(), - NameAVP = #{attribute => ?NAME_ATTR_FOR_INSTANCE, value => Name}, + NameAVP = #{attribute => ?NAME_ATTR_INSTANCE, value => Name}, CodeAVP = #{attribute => LCAttr, value => Code}, BaseAVP = #{attribute => BLAttr, value => BaseNref}, Node = #node{ @@ -675,18 +660,18 @@ do_register_dialect(Code, Name, BaseCode, State) -> id = ArcId1, kind = instantiation, source_nref = Nref, - characterization = ?CLASS_MEMBERSHIP_ARC, + characterization = ?ARC_INST_TO_CLASS, target_nref = LHNref, - reciprocal = ?INSTANCE_MEMBERSHIP_ARC, + reciprocal = ?ARC_CLASS_TO_INST, avps = [] }, C2I = #relationship{ id = ArcId2, kind = instantiation, source_nref = LHNref, - characterization = ?INSTANCE_MEMBERSHIP_ARC, + characterization = ?ARC_CLASS_TO_INST, target_nref = Nref, - reciprocal = ?CLASS_MEMBERSHIP_ARC, + reciprocal = ?ARC_INST_TO_CLASS, avps = [] }, F = fun() -> diff --git a/apps/graphdb/src/graphdb_mgr.erl b/apps/graphdb/src/graphdb_mgr.erl index 3490a22..28303f2 100644 --- a/apps/graphdb/src/graphdb_mgr.erl +++ b/apps/graphdb/src/graphdb_mgr.erl @@ -48,6 +48,7 @@ %%--------------------------------------------------------------------- %% Include files %%--------------------------------------------------------------------- +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- %% Macro Functions @@ -482,8 +483,7 @@ check_category_guard(Nref) -> %% Instance-to-class membership (instantiation): %% 29 -- instance -> class %%----------------------------------------------------------------------------- --define(PARENT_ARCS, [21, 23, 25, 27]). --define(CLASS_MEMBERSHIP_ARC, 29). +-define(PARENT_ARCS, [?ARC_CAT_PARENT, ?ARC_ATTR_PARENT, ?ARC_CLS_PARENT, ?ARC_INST_PARENT]). %%----------------------------------------------------------------------------- %% expected_parents(Nref) -> [integer()] @@ -515,7 +515,7 @@ expected_classes(Nref) -> #relationship.source_nref), [A#relationship.target_nref || A <- Arcs, A#relationship.kind =:= instantiation, - A#relationship.characterization =:= ?CLASS_MEMBERSHIP_ARC]. + A#relationship.characterization =:= ?ARC_INST_TO_CLASS]. %%----------------------------------------------------------------------------- From cd642d5c78b147bf01560d4dd3591751e11f88aa Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 06:04:01 -0400 Subject: [PATCH 6/8] Task 6: migrate test files to shared graphdb_nrefs.hrl macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add -include_lib("graphdb/include/graphdb_nrefs.hrl") to 7 test files and replace all raw scaffold nref integers with named macros: attribute => 17/18/19/20/31 → ?NAME_ATTR_CATEGORY/ATTRIBUTE/CLASS/INSTANCE, ?ARC_TEMPLATE characterization/reciprocal → ?ARC_CAT/ATTR/CLS/INST_* arc macros mnesia:read(nodes, N) → named macros 10000 (English nref) → ?NREF_ENGLISH language subcategory tuples → ?NREF_HUMAN_LANGS etc. NamesGroup / RelGroup lists → full macro expansion 320 tests (217 CT + 103 EUnit), all green. Co-Authored-By: Claude Sonnet 4.6 --- apps/graphdb/test/graphdb_attr_SUITE.erl | 50 ++++++---- apps/graphdb/test/graphdb_bootstrap_SUITE.erl | 49 +++++----- apps/graphdb/test/graphdb_class_SUITE.erl | 97 ++++++++++--------- apps/graphdb/test/graphdb_instance_SUITE.erl | 37 +++---- apps/graphdb/test/graphdb_instance_tests.erl | 7 +- apps/graphdb/test/graphdb_language_SUITE.erl | 49 +++++----- apps/graphdb/test/graphdb_mgr_SUITE.erl | 15 +-- 7 files changed, 160 insertions(+), 144 deletions(-) diff --git a/apps/graphdb/test/graphdb_attr_SUITE.erl b/apps/graphdb/test/graphdb_attr_SUITE.erl index da577ff..1d9219e 100644 --- a/apps/graphdb/test/graphdb_attr_SUITE.erl +++ b/apps/graphdb/test/graphdb_attr_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- @@ -290,7 +291,7 @@ template_avp_marker_stamped(_Config) -> {ok, _} = graphdb_attr:start_link(), {ok, #{relationship_avp := RaNref}} = graphdb_attr:seeded_nrefs(), {atomic, [Node]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 31) + mnesia:read(nodes, ?ARC_TEMPLATE) end), AVPs = Node#node.attribute_value_pairs, ?assert(lists:member(#{attribute => RaNref, value => true}, AVPs)). @@ -302,13 +303,13 @@ template_avp_marker_stamped(_Config) -> template_avp_marker_idempotent(_Config) -> {ok, _} = graphdb_attr:start_link(), {atomic, [Before]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 31) + mnesia:read(nodes, ?ARC_TEMPLATE) end), BeforeAVPs = Before#node.attribute_value_pairs, ok = gen_server:stop(graphdb_attr), {ok, _} = graphdb_attr:start_link(), {atomic, [After]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 31) + mnesia:read(nodes, ?ARC_TEMPLATE) end), ?assertEqual(BeforeAVPs, After#node.attribute_value_pairs). @@ -326,7 +327,7 @@ create_name_attribute_basic(_Config) -> {ok, Node} = graphdb_attr:get_attribute(Nref), ?assertEqual(attribute, Node#node.kind), ?assertEqual([6], Node#node.parents), - ?assert(lists:member(#{attribute => 18, value => "TestName"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "TestName"}, Node#node.attribute_value_pairs)). %%----------------------------------------------------------------------------- @@ -340,7 +341,7 @@ create_literal_attribute_stores_type(_Config) -> {ok, Node} = graphdb_attr:get_attribute(Nref), ?assertEqual([7], Node#node.parents), AVPs = Node#node.attribute_value_pairs, - ?assert(lists:member(#{attribute => 18, value => "Weight"}, AVPs)), + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Weight"}, AVPs)), ?assert(lists:member(#{attribute => Lt, value => kilogram}, AVPs)). %%----------------------------------------------------------------------------- @@ -359,9 +360,9 @@ create_relationship_attribute_pair(_Config) -> {ok, Rev} = graphdb_attr:get_attribute(RevNref), ?assertEqual([8], Fwd#node.parents), ?assertEqual([8], Rev#node.parents), - ?assert(lists:member(#{attribute => 18, value => "Makes"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Makes"}, Fwd#node.attribute_value_pairs)), - ?assert(lists:member(#{attribute => 18, value => "MadeBy"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "MadeBy"}, Rev#node.attribute_value_pairs)), ?assert(lists:member(#{attribute => Tk, value => class}, Fwd#node.attribute_value_pairs)), @@ -393,10 +394,10 @@ create_relationship_attribute_pair_atomic(_Config) -> end), FwdInbound = [R || R <- Out, R#relationship.target_nref =:= FwdNref, - R#relationship.characterization =:= 24], + R#relationship.characterization =:= ?ARC_ATTR_CHILD], RevInbound = [R || R <- Out, R#relationship.target_nref =:= RevNref, - R#relationship.characterization =:= 24], + R#relationship.characterization =:= ?ARC_ATTR_CHILD], ?assertEqual(1, length(FwdInbound)), ?assertEqual(1, length(RevInbound)), ?assertEqual(taxonomy, (hd(FwdInbound))#relationship.kind). @@ -418,7 +419,7 @@ create_relationship_type_basic(_Config) -> {ok, Nref} = graphdb_attr:create_relationship_type("Ownership"), {ok, Node} = graphdb_attr:get_attribute(Nref), ?assertEqual([8], Node#node.parents), - ?assert(lists:member(#{attribute => 18, value => "Ownership"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Ownership"}, Node#node.attribute_value_pairs)). %%----------------------------------------------------------------------------- @@ -439,8 +440,8 @@ new_attribute_writes_taxonomy_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= Nref andalso - R#relationship.characterization =:= 24 andalso - R#relationship.reciprocal =:= 23 + R#relationship.characterization =:= ?ARC_ATTR_CHILD andalso + R#relationship.reciprocal =:= ?ARC_ATTR_PARENT end, ParentOut)), %% Child (Nref) -> Parent (6) with char=23 should exist @@ -449,8 +450,8 @@ new_attribute_writes_taxonomy_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= 6 andalso - R#relationship.characterization =:= 23 andalso - R#relationship.reciprocal =:= 24 + R#relationship.characterization =:= ?ARC_ATTR_PARENT andalso + R#relationship.reciprocal =:= ?ARC_ATTR_CHILD end, ChildOut)). @@ -612,8 +613,8 @@ attribute_type_of_returns_kind(_Config) -> %% Bootstrap-derived: 17 (Name, under Names subtree) and 21 (Parent %% category arc label, under Relationships subtree) and 7 (Literals %% itself). - ?assertEqual({ok, name}, graphdb_attr:attribute_type_of(17)), - ?assertEqual({ok, relationship}, graphdb_attr:attribute_type_of(21)), + ?assertEqual({ok, name}, graphdb_attr:attribute_type_of(?NAME_ATTR_CATEGORY)), + ?assertEqual({ok, relationship}, graphdb_attr:attribute_type_of(?ARC_CAT_PARENT)), ?assertEqual({ok, literal}, graphdb_attr:attribute_type_of(7)). %%----------------------------------------------------------------------------- @@ -640,7 +641,10 @@ attribute_type_of_non_attribute(_Config) -> bootstrap_attributes_retro_stamped(_Config) -> {ok, _} = graphdb_attr:start_link(), %% Names subtree -- 6, 9-12, 17-20 should be `name`. - NamesGroup = [6, 9, 10, 11, 12, 17, 18, 19, 20], + NamesGroup = [?NREF_NAMES, ?NREF_CAT_NAME_ATTRS, ?NREF_ATTR_NAME_ATTRS, + ?NREF_CLS_NAME_ATTRS, ?NREF_INST_NAME_ATTRS, + ?NAME_ATTR_CATEGORY, ?NAME_ATTR_ATTRIBUTE, ?NAME_ATTR_CLASS, + ?NAME_ATTR_INSTANCE], lists:foreach(fun(N) -> ?assertEqual({ok, name}, graphdb_attr:attribute_type_of(N)) end, NamesGroup), @@ -649,7 +653,13 @@ bootstrap_attributes_retro_stamped(_Config) -> ?assertEqual({ok, literal}, graphdb_attr:attribute_type_of(7)), %% Relationships subtree -- 8, 13-16, 21-31 should be `relationship`. - RelGroup = [8, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], + RelGroup = [?NREF_RELATIONSHIPS, ?NREF_CAT_REL_ATTRS, ?NREF_ATTR_REL_ATTRS, + ?NREF_CLS_REL_ATTRS, ?NREF_INST_REL_ATTRS, + ?ARC_CAT_PARENT, ?ARC_CAT_CHILD, + ?ARC_ATTR_PARENT, ?ARC_ATTR_CHILD, + ?ARC_CLS_PARENT, ?ARC_CLS_CHILD, + ?ARC_INST_PARENT, ?ARC_INST_CHILD, + ?ARC_INST_TO_CLASS, ?ARC_CLASS_TO_INST, ?ARC_TEMPLATE], lists:foreach(fun(N) -> ?assertEqual({ok, relationship}, graphdb_attr:attribute_type_of(N)) end, RelGroup). @@ -662,13 +672,13 @@ bootstrap_attributes_retro_stamped(_Config) -> retro_stamp_idempotent_on_restart(_Config) -> {ok, _} = graphdb_attr:start_link(), {atomic, [Before6]} = mnesia:transaction(fun() -> mnesia:read(nodes, 6) end), - {atomic, [Before24]} = mnesia:transaction(fun() -> mnesia:read(nodes, 24) end), + {atomic, [Before24]} = mnesia:transaction(fun() -> mnesia:read(nodes, ?ARC_ATTR_CHILD) end), ok = gen_server:stop(graphdb_attr), {ok, _} = graphdb_attr:start_link(), {atomic, [After6]} = mnesia:transaction(fun() -> mnesia:read(nodes, 6) end), - {atomic, [After24]} = mnesia:transaction(fun() -> mnesia:read(nodes, 24) end), + {atomic, [After24]} = mnesia:transaction(fun() -> mnesia:read(nodes, ?ARC_ATTR_CHILD) end), ?assertEqual(Before6#node.attribute_value_pairs, After6#node.attribute_value_pairs), ?assertEqual(Before24#node.attribute_value_pairs, diff --git a/apps/graphdb/test/graphdb_bootstrap_SUITE.erl b/apps/graphdb/test/graphdb_bootstrap_SUITE.erl index 604e003..08455da 100644 --- a/apps/graphdb/test/graphdb_bootstrap_SUITE.erl +++ b/apps/graphdb/test/graphdb_bootstrap_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- @@ -267,7 +268,7 @@ load_root_node_correct(_Config) -> ?assertEqual(1, Root#node.nref), ?assertEqual(category, Root#node.kind), ?assertEqual([], Root#node.parents), - ?assertEqual([#{attribute => 17, value => "Root"}], + ?assertEqual([#{attribute => ?NAME_ATTR_CATEGORY, value => "Root"}], Root#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -276,12 +277,12 @@ load_root_node_correct(_Config) -> load_attribute_node_correct(_Config) -> ok = graphdb_bootstrap:load(), {atomic, [Node]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 18) + mnesia:read(nodes, ?NAME_ATTR_ATTRIBUTE) end), ?assertEqual(18, Node#node.nref), ?assertEqual(attribute, Node#node.kind), ?assertEqual([10], Node#node.parents), %% parent: Attribute Name Attributes - ?assertEqual([#{attribute => 18, value => "Name"}], + ?assertEqual([#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Name"}], Node#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -292,12 +293,12 @@ load_attribute_node_correct(_Config) -> load_template_avp_node_correct(_Config) -> ok = graphdb_bootstrap:load(), {atomic, [Node]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 31) + mnesia:read(nodes, ?ARC_TEMPLATE) end), - ?assertEqual(31, Node#node.nref), + ?assertEqual(?ARC_TEMPLATE, Node#node.nref), ?assertEqual(attribute, Node#node.kind), ?assertEqual([16], Node#node.parents), %% Instance Relationships subtree - ?assertEqual([#{attribute => 18, value => "Template"}], + ?assertEqual([#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Template"}], Node#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -306,10 +307,10 @@ load_template_avp_node_correct(_Config) -> load_language_subcategories(_Config) -> ok = graphdb_bootstrap:load(), Expected = [ - {32, "Human Languages"}, - {33, "Formal Languages"}, - {34, "Diagram Languages"}, - {35, "Renderers"} + {?NREF_HUMAN_LANGS, "Human Languages"}, + {?NREF_FORMAL_LANGS, "Formal Languages"}, + {?NREF_DIAGRAM_LANGS,"Diagram Languages"}, + {?NREF_RENDERERS, "Renderers"} ], lists:foreach(fun({Nref, Name}) -> {atomic, [Node]} = mnesia:transaction(fun() -> @@ -318,7 +319,7 @@ load_language_subcategories(_Config) -> ?assertEqual(Nref, Node#node.nref), ?assertEqual(category, Node#node.kind), ?assertEqual([4], Node#node.parents), - ?assertEqual([#{attribute => 17, value => Name}], + ?assertEqual([#{attribute => ?NAME_ATTR_CATEGORY, value => Name}], Node#node.attribute_value_pairs) end, Expected), %% Languages (nref 4) has exactly these four children via char=22 (Child/CatRel) @@ -328,8 +329,8 @@ load_language_subcategories(_Config) -> ChildNrefs = lists:sort([A#relationship.target_nref || A <- ChildArcs, A#relationship.kind =:= composition, - A#relationship.characterization =:= 22]), - ?assertEqual([32, 33, 34, 35], ChildNrefs). + A#relationship.characterization =:= ?ARC_CAT_CHILD]), + ?assertEqual([?NREF_HUMAN_LANGS, ?NREF_FORMAL_LANGS, ?NREF_DIAGRAM_LANGS, ?NREF_RENDERERS], ChildNrefs). %%----------------------------------------------------------------------------- %% Verify Root's children via the compositional arcs (char=22, kind=composition). @@ -342,7 +343,7 @@ load_category_children(_Config) -> ChildNrefs = lists:sort([A#relationship.target_nref || A <- Arcs, A#relationship.kind =:= composition, - A#relationship.characterization =:= 22]), + A#relationship.characterization =:= ?ARC_CAT_CHILD]), %% Root's children: Attributes(2), Classes(3), Languages(4), Projects(5) ?assertEqual([2, 3, 4, 5], ChildNrefs), %% Each child node is a category and lists Root in its parents cache @@ -362,11 +363,11 @@ load_relationship_structure(_Config) -> mnesia:index_read(relationships, 1, #relationship.source_nref) end), ChildArcs = [R || R <- Fwd, - R#relationship.characterization =:= 22, + R#relationship.characterization =:= ?ARC_CAT_CHILD, R#relationship.target_nref =:= 2], ?assertEqual(1, length(ChildArcs)), [Arc] = ChildArcs, - ?assertEqual(21, Arc#relationship.reciprocal), + ?assertEqual(?ARC_CAT_PARENT, Arc#relationship.reciprocal), ?assertEqual([], Arc#relationship.avps). %%----------------------------------------------------------------------------- @@ -440,9 +441,9 @@ load_idempotent(_Config) -> load_english_instance(_Config) -> ok = graphdb_bootstrap:load(), {atomic, [Eng]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 10000) + mnesia:read(nodes, ?NREF_ENGLISH) end), - ?assertEqual(10000, Eng#node.nref), + ?assertEqual(?NREF_ENGLISH, Eng#node.nref), ?assertEqual(instance, Eng#node.kind), %% Find lang_code attribute nref by name (do not hardcode the runtime nref) LangCodeNref = find_attribute_nref_by_name("lang_code"), @@ -468,18 +469,18 @@ load_english_class_membership(_Config) -> LangHumanNref = find_class_nref_by_name("Human Language"), %% English's classes cache must contain LangHuman nref {atomic, [Eng]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 10000) + mnesia:read(nodes, ?NREF_ENGLISH) end), ?assert(lists:member(LangHumanNref, Eng#node.classes)), %% English's compositional parent is Human Languages (nref 32) - ?assertEqual([32], Eng#node.parents), + ?assertEqual([?NREF_HUMAN_LANGS], Eng#node.parents), %% Instantiation arc English -> Human Language exists {atomic, MemberArcs} = mnesia:transaction(fun() -> - mnesia:index_read(relationships, 10000, #relationship.source_nref) + mnesia:index_read(relationships, ?NREF_ENGLISH, #relationship.source_nref) end), ClassArcs = [A || A <- MemberArcs, A#relationship.kind =:= instantiation, - A#relationship.characterization =:= 29, + A#relationship.characterization =:= ?ARC_INST_TO_CLASS, A#relationship.target_nref =:= LangHumanNref], ?assertEqual(1, length(ClassArcs)). @@ -610,7 +611,7 @@ find_attribute_nref_by_name(Name) -> {atomic, Matches} = mnesia:transaction(fun() -> mnesia:foldl(fun(N, Acc) -> case N#node.kind =:= attribute andalso - lists:member(#{attribute => 18, value => Name}, + lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => Name}, N#node.attribute_value_pairs) of true -> [N#node.nref | Acc]; false -> Acc @@ -633,7 +634,7 @@ find_class_nref_by_name(Name) -> {atomic, Matches} = mnesia:transaction(fun() -> mnesia:foldl(fun(N, Acc) -> case N#node.kind =:= class andalso - lists:member(#{attribute => 19, value => Name}, + lists:member(#{attribute => ?NAME_ATTR_CLASS, value => Name}, N#node.attribute_value_pairs) of true -> [N#node.nref | Acc]; false -> Acc diff --git a/apps/graphdb/test/graphdb_class_SUITE.erl b/apps/graphdb/test/graphdb_class_SUITE.erl index 2bb778e..7f78c04 100644 --- a/apps/graphdb/test/graphdb_class_SUITE.erl +++ b/apps/graphdb/test/graphdb_class_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- @@ -279,7 +280,7 @@ create_class_top_level(_Config) -> {ok, Node} = graphdb_class:get_class(Nref), ?assertEqual(class, Node#node.kind), ?assertEqual([3], Node#node.parents), - ?assertEqual([#{attribute => 19, value => "Animal"}], + ?assertEqual([#{attribute => ?NAME_ATTR_CLASS, value => "Animal"}], Node#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -328,8 +329,8 @@ create_class_writes_compositional_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= Nref andalso - R#relationship.characterization =:= 26 andalso - R#relationship.reciprocal =:= 25 + R#relationship.characterization =:= ?ARC_CLS_CHILD andalso + R#relationship.reciprocal =:= ?ARC_CLS_PARENT end, ParentOut)), %% Child (Nref) -> Parent (3) with char=25 should exist @@ -338,8 +339,8 @@ create_class_writes_compositional_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= 3 andalso - R#relationship.characterization =:= 25 andalso - R#relationship.reciprocal =:= 26 + R#relationship.characterization =:= ?ARC_CLS_PARENT andalso + R#relationship.reciprocal =:= ?ARC_CLS_CHILD end, ChildOut)). @@ -354,7 +355,7 @@ create_class_auto_creates_default_template(_Config) -> {ok, TemplateNode} = graphdb_class:get_template(TemplateNref), ?assertEqual(template, TemplateNode#node.kind), ?assertEqual([ClassNref], TemplateNode#node.parents), - ?assert(lists:member(#{attribute => 19, value => "default"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_CLASS, value => "default"}, TemplateNode#node.attribute_value_pairs)). @@ -372,7 +373,7 @@ add_template_basic(_Config) -> {ok, Node} = graphdb_class:get_template(TmplNref), ?assertEqual(template, Node#node.kind), ?assertEqual([ClassNref], Node#node.parents), - ?assert(lists:member(#{attribute => 19, value => "biological"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_CLASS, value => "biological"}, Node#node.attribute_value_pairs)). %%----------------------------------------------------------------------------- @@ -424,7 +425,7 @@ templates_for_class_lists_all(_Config) -> {ok, _} = graphdb_class:add_template(ClassNref, "social"), {ok, Templates} = graphdb_class:templates_for_class(ClassNref), Names = [V || #node{attribute_value_pairs = AVPs} <- Templates, - #{attribute := 19, value := V} <- AVPs], + #{attribute := ?NAME_ATTR_CLASS, value := V} <- AVPs], ?assertEqual(lists:sort(["default", "biological", "social"]), lists:sort(Names)). @@ -436,7 +437,7 @@ default_template_returns_default(_Config) -> {ok, ClassNref} = graphdb_class:create_class("Animal", 3), {ok, TmplNref} = graphdb_class:default_template(ClassNref), {ok, Node} = graphdb_class:get_template(TmplNref), - ?assert(lists:member(#{attribute => 19, value => "default"}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_CLASS, value => "default"}, Node#node.attribute_value_pairs)). %%----------------------------------------------------------------------------- @@ -494,9 +495,9 @@ add_qc_basic(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, ClassNref} = graphdb_class:create_class("Color", 3), %% Use bootstrap attribute nref 18 (Name/attribute) as a QC for testing - ok = graphdb_class:add_qualifying_characteristic(ClassNref, 18), + ok = graphdb_class:add_qualifying_characteristic(ClassNref, ?NAME_ATTR_ATTRIBUTE), {ok, Node} = graphdb_class:get_class(ClassNref), - ?assert(lists:member(#{attribute => 18, value => undefined}, + ?assert(lists:member(#{attribute => ?NAME_ATTR_ATTRIBUTE, value => undefined}, Node#node.attribute_value_pairs)). %%----------------------------------------------------------------------------- @@ -505,12 +506,12 @@ add_qc_basic(_Config) -> add_qc_idempotent(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, ClassNref} = graphdb_class:create_class("Size", 3), - ok = graphdb_class:add_qualifying_characteristic(ClassNref, 18), - ok = graphdb_class:add_qualifying_characteristic(ClassNref, 18), + ok = graphdb_class:add_qualifying_characteristic(ClassNref, ?NAME_ATTR_ATTRIBUTE), + ok = graphdb_class:add_qualifying_characteristic(ClassNref, ?NAME_ATTR_ATTRIBUTE), {ok, Node} = graphdb_class:get_class(ClassNref), QcCount = length([1 || #{attribute := A} <- Node#node.attribute_value_pairs, - A =:= 18]), + A =:= ?NAME_ATTR_ATTRIBUTE]), ?assertEqual(1, QcCount). %%----------------------------------------------------------------------------- @@ -520,7 +521,7 @@ add_qc_rejects_non_class(_Config) -> {ok, _} = graphdb_class:start_link(), %% Nref 6 is an attribute node ?assertMatch({error, {not_a_class, _}}, - graphdb_class:add_qualifying_characteristic(6, 18)). + graphdb_class:add_qualifying_characteristic(?NREF_NAMES, ?NAME_ATTR_ATTRIBUTE)). %%----------------------------------------------------------------------------- %% add_qualifying_characteristic rejects non-attribute nrefs. @@ -646,8 +647,8 @@ add_superclass_writes_taxonomy_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= B andalso - R#relationship.characterization =:= 25 andalso - R#relationship.reciprocal =:= 26 andalso + R#relationship.characterization =:= ?ARC_CLS_PARENT andalso + R#relationship.reciprocal =:= ?ARC_CLS_CHILD andalso R#relationship.kind =:= taxonomy end, ChildOut)), @@ -657,8 +658,8 @@ add_superclass_writes_taxonomy_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= Child andalso - R#relationship.characterization =:= 26 andalso - R#relationship.reciprocal =:= 25 andalso + R#relationship.characterization =:= ?ARC_CLS_CHILD andalso + R#relationship.reciprocal =:= ?ARC_CLS_PARENT andalso R#relationship.kind =:= taxonomy end, BOut)). @@ -747,23 +748,23 @@ ancestors_dedupes_diamond_inheritance(_Config) -> inherited_qcs_multi_parent(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, A} = graphdb_class:create_class("A", 3), - ok = graphdb_class:add_qualifying_characteristic(A, 17), + ok = graphdb_class:add_qualifying_characteristic(A, ?NAME_ATTR_CATEGORY), {ok, B} = graphdb_class:create_class("B", 3), - ok = graphdb_class:add_qualifying_characteristic(B, 18), + ok = graphdb_class:add_qualifying_characteristic(B, ?NAME_ATTR_ATTRIBUTE), {ok, C} = graphdb_class:create_class("C", A), ok = graphdb_class:add_superclass(C, B), - ok = graphdb_class:add_qualifying_characteristic(C, 20), + ok = graphdb_class:add_qualifying_characteristic(C, ?NAME_ATTR_INSTANCE), {ok, QcPairs} = graphdb_class:inherited_qcs(C), %% Local (20), then nearest parents A (17), B (18) — all value=>undefined. - ?assert(lists:member({20, undefined}, QcPairs)), - ?assert(lists:member({17, undefined}, QcPairs)), - ?assert(lists:member({18, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_INSTANCE, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_CATEGORY, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_ATTRIBUTE, undefined}, QcPairs)), %% Order: C's local QC before ancestors; BFS visits A before B (A is the %% creation parent, B added via add_superclass), so 17 appears before 18. Attrs = [A2 || {A2, _} <- QcPairs], - ?assert(lists_index_of(20, Attrs) < lists_index_of(17, Attrs)), - ?assert(lists_index_of(20, Attrs) < lists_index_of(18, Attrs)), - ?assert(lists_index_of(17, Attrs) < lists_index_of(18, Attrs)). + ?assert(lists_index_of(?NAME_ATTR_INSTANCE, Attrs) < lists_index_of(?NAME_ATTR_CATEGORY, Attrs)), + ?assert(lists_index_of(?NAME_ATTR_INSTANCE, Attrs) < lists_index_of(?NAME_ATTR_ATTRIBUTE, Attrs)), + ?assert(lists_index_of(?NAME_ATTR_CATEGORY, Attrs) < lists_index_of(?NAME_ATTR_ATTRIBUTE, Attrs)). %%----------------------------------------------------------------------------- %% class_in_ancestry finds a parent added via add_superclass. @@ -789,13 +790,13 @@ class_in_ancestry_via_added_parent(_Config) -> inherited_qcs_local_only(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, ClassNref} = graphdb_class:create_class("Color", 3), - ok = graphdb_class:add_qualifying_characteristic(ClassNref, 17), - ok = graphdb_class:add_qualifying_characteristic(ClassNref, 18), + ok = graphdb_class:add_qualifying_characteristic(ClassNref, ?NAME_ATTR_CATEGORY), + ok = graphdb_class:add_qualifying_characteristic(ClassNref, ?NAME_ATTR_ATTRIBUTE), {ok, QcPairs} = graphdb_class:inherited_qcs(ClassNref), %% Class has name AVP (attr=19, value="Color") plus two QC AVPs. %% inherited_qcs filters out the name AVP; only QC attrs 17 and 18 appear. - ?assert(lists:member({17, undefined}, QcPairs)), - ?assert(lists:member({18, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_CATEGORY, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_ATTRIBUTE, undefined}, QcPairs)), ?assertNot(lists:keymember(19, 1, QcPairs)). %%----------------------------------------------------------------------------- @@ -804,21 +805,21 @@ inherited_qcs_local_only(_Config) -> inherited_qcs_from_ancestors(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, Animal} = graphdb_class:create_class("Animal", 3), - ok = graphdb_class:add_qualifying_characteristic(Animal, 17), + ok = graphdb_class:add_qualifying_characteristic(Animal, ?NAME_ATTR_CATEGORY), {ok, Mammal} = graphdb_class:create_class("Mammal", Animal), - ok = graphdb_class:add_qualifying_characteristic(Mammal, 18), + ok = graphdb_class:add_qualifying_characteristic(Mammal, ?NAME_ATTR_ATTRIBUTE), {ok, Whale} = graphdb_class:create_class("Whale", Mammal), - ok = graphdb_class:add_qualifying_characteristic(Whale, 20), + ok = graphdb_class:add_qualifying_characteristic(Whale, ?NAME_ATTR_INSTANCE), {ok, QcPairs} = graphdb_class:inherited_qcs(Whale), %% QC attrs 20 (local), 18 (from Mammal), 17 (from Animal) must appear. %% Dedup: each appears exactly once; local entry comes first. - ?assert(lists:member({20, undefined}, QcPairs)), - ?assert(lists:member({18, undefined}, QcPairs)), - ?assert(lists:member({17, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_INSTANCE, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_ATTRIBUTE, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_CATEGORY, undefined}, QcPairs)), %% 20 must appear before 18, and 18 must appear before 17. Attrs = [A || {A, _} <- QcPairs], - ?assert(lists_index_of(20, Attrs) < lists_index_of(18, Attrs)), - ?assert(lists_index_of(18, Attrs) < lists_index_of(17, Attrs)). + ?assert(lists_index_of(?NAME_ATTR_INSTANCE, Attrs) < lists_index_of(?NAME_ATTR_ATTRIBUTE, Attrs)), + ?assert(lists_index_of(?NAME_ATTR_ATTRIBUTE, Attrs) < lists_index_of(?NAME_ATTR_CATEGORY, Attrs)). %%----------------------------------------------------------------------------- %% inherited_qcs deduplicates: if a child has the same QC as an ancestor, @@ -827,19 +828,19 @@ inherited_qcs_from_ancestors(_Config) -> inherited_qcs_deduplicates(_Config) -> {ok, _} = graphdb_class:start_link(), {ok, Parent} = graphdb_class:create_class("Parent", 3), - ok = graphdb_class:add_qualifying_characteristic(Parent, 17), - ok = graphdb_class:add_qualifying_characteristic(Parent, 18), + ok = graphdb_class:add_qualifying_characteristic(Parent, ?NAME_ATTR_CATEGORY), + ok = graphdb_class:add_qualifying_characteristic(Parent, ?NAME_ATTR_ATTRIBUTE), {ok, Child} = graphdb_class:create_class("Child", Parent), - ok = graphdb_class:add_qualifying_characteristic(Child, 18), - ok = graphdb_class:add_qualifying_characteristic(Child, 20), + ok = graphdb_class:add_qualifying_characteristic(Child, ?NAME_ATTR_ATTRIBUTE), + ok = graphdb_class:add_qualifying_characteristic(Child, ?NAME_ATTR_INSTANCE), {ok, QcPairs} = graphdb_class:inherited_qcs(Child), %% 18 appears only once (from Child, not duplicated from Parent). - Count18 = length([1 || {A, _} <- QcPairs, A =:= 18]), + Count18 = length([1 || {A, _} <- QcPairs, A =:= ?NAME_ATTR_ATTRIBUTE]), ?assertEqual(1, Count18), %% 17 (from Parent only) and 20 (from Child only) also present. - ?assert(lists:member({17, undefined}, QcPairs)), - ?assert(lists:member({18, undefined}, QcPairs)), - ?assert(lists:member({20, undefined}, QcPairs)). + ?assert(lists:member({?NAME_ATTR_CATEGORY, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_ATTRIBUTE, undefined}, QcPairs)), + ?assert(lists:member({?NAME_ATTR_INSTANCE, undefined}, QcPairs)). %%============================================================================= diff --git a/apps/graphdb/test/graphdb_instance_SUITE.erl b/apps/graphdb/test/graphdb_instance_SUITE.erl index e969218..7617629 100644 --- a/apps/graphdb/test/graphdb_instance_SUITE.erl +++ b/apps/graphdb/test/graphdb_instance_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- @@ -302,7 +303,7 @@ create_instance_basic(_Config) -> {ok, Node} = graphdb_instance:get_instance(InstNref), ?assertEqual(instance, Node#node.kind), ?assertEqual([5], Node#node.parents), - ?assertEqual([#{attribute => 20, value => "Car1"}], + ?assertEqual([#{attribute => ?NAME_ATTR_INSTANCE, value => "Car1"}], Node#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -342,8 +343,8 @@ create_instance_writes_membership_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= ClassNref andalso - R#relationship.characterization =:= 29 andalso - R#relationship.reciprocal =:= 30 + R#relationship.characterization =:= ?ARC_INST_TO_CLASS andalso + R#relationship.reciprocal =:= ?ARC_CLASS_TO_INST end, InstOut)), %% Class -> Instance (char=30, reciprocal=29) @@ -353,8 +354,8 @@ create_instance_writes_membership_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= InstNref andalso - R#relationship.characterization =:= 30 andalso - R#relationship.reciprocal =:= 29 + R#relationship.characterization =:= ?ARC_CLASS_TO_INST andalso + R#relationship.reciprocal =:= ?ARC_INST_TO_CLASS end, ClassOut)). %%----------------------------------------------------------------------------- @@ -370,8 +371,8 @@ create_instance_writes_compositional_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= InstNref andalso - R#relationship.characterization =:= 28 andalso - R#relationship.reciprocal =:= 27 + R#relationship.characterization =:= ?ARC_INST_CHILD andalso + R#relationship.reciprocal =:= ?ARC_INST_PARENT end, ParentOut)), %% Child (InstNref) -> Parent (5) with char=27 @@ -381,8 +382,8 @@ create_instance_writes_compositional_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= 5 andalso - R#relationship.characterization =:= 27 andalso - R#relationship.reciprocal =:= 28 + R#relationship.characterization =:= ?ARC_INST_PARENT andalso + R#relationship.reciprocal =:= ?ARC_INST_CHILD end, ChildOut)). @@ -456,7 +457,7 @@ add_relationship_stamps_template_avp(_Config) -> R#relationship.characterization =:= Char, R#relationship.target_nref =:= B], ?assertEqual(connection, Fwd#relationship.kind), - ?assert(lists:member(#{attribute => 31, value => DefaultTmpl}, + ?assert(lists:member(#{attribute => ?ARC_TEMPLATE, value => DefaultTmpl}, Fwd#relationship.avps)). %%----------------------------------------------------------------------------- @@ -477,7 +478,7 @@ add_relationship_explicit_template(_Config) -> [Fwd] = [R || R <- ARels, R#relationship.characterization =:= Char, R#relationship.target_nref =:= B], - ?assert(lists:member(#{attribute => 31, value => AltTmpl}, + ?assert(lists:member(#{attribute => ?ARC_TEMPLATE, value => AltTmpl}, Fwd#relationship.avps)). %%----------------------------------------------------------------------------- @@ -612,7 +613,7 @@ add_relationship_stamps_user_avps(_Config) -> [Fwd] = [R || R <- ARels, R#relationship.characterization =:= Char, R#relationship.target_nref =:= B], - ?assert(lists:member(#{attribute => 31, value => DefaultTmpl}, + ?assert(lists:member(#{attribute => ?ARC_TEMPLATE, value => DefaultTmpl}, Fwd#relationship.avps)), ?assert(lists:member(UserAVP, Fwd#relationship.avps)). @@ -671,7 +672,7 @@ add_relationship_default_avps_empty(_Config) -> [Fwd] = [R || R <- ARels, R#relationship.characterization =:= Char, R#relationship.target_nref =:= B], - ?assertEqual([#{attribute => 31, value => DefaultTmpl}], + ?assertEqual([#{attribute => ?ARC_TEMPLATE, value => DefaultTmpl}], Fwd#relationship.avps). @@ -770,7 +771,7 @@ resolve_value_local(_Config) -> {ok, ClassNref} = graphdb_class:create_class("Thing", 3), {ok, InstNref} = graphdb_instance:create_instance("T1", ClassNref, 5), %% The name attribute (20) was set by create_instance - ?assertEqual({ok, "T1"}, graphdb_instance:resolve_value(InstNref, 20)). + ?assertEqual({ok, "T1"}, graphdb_instance:resolve_value(InstNref, ?NAME_ATTR_INSTANCE)). %%----------------------------------------------------------------------------- %% resolve_value finds a value from the class node's AVPs. @@ -951,8 +952,8 @@ add_class_membership_writes_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= ClassB andalso - R#relationship.characterization =:= 29 andalso - R#relationship.reciprocal =:= 30 + R#relationship.characterization =:= ?ARC_INST_TO_CLASS andalso + R#relationship.reciprocal =:= ?ARC_CLASS_TO_INST end, InstOut)), %% ClassB -> Instance (char=30, reciprocal=29) @@ -961,8 +962,8 @@ add_class_membership_writes_arcs(_Config) -> end), ?assert(lists:any(fun(R) -> R#relationship.target_nref =:= Inst andalso - R#relationship.characterization =:= 30 andalso - R#relationship.reciprocal =:= 29 + R#relationship.characterization =:= ?ARC_CLASS_TO_INST andalso + R#relationship.reciprocal =:= ?ARC_INST_TO_CLASS end, ClassOut)). %%----------------------------------------------------------------------------- diff --git a/apps/graphdb/test/graphdb_instance_tests.erl b/apps/graphdb/test/graphdb_instance_tests.erl index d02bc12..78920d7 100644 --- a/apps/graphdb/test/graphdb_instance_tests.erl +++ b/apps/graphdb/test/graphdb_instance_tests.erl @@ -11,6 +11,7 @@ -module(graphdb_instance_tests). -include_lib("eunit/include/eunit.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%============================================================================= @@ -35,9 +36,9 @@ find_avp_value_first_match_wins_test() -> find_avp_value_among_many_test() -> AVPs = [#{attribute => 10, value => "a"}, - #{attribute => 20, value => "b"}, - #{attribute => 30, value => "c"}], - ?assertEqual({ok, "b"}, graphdb_instance:find_avp_value(AVPs, 20)). + #{attribute => ?NAME_ATTR_INSTANCE, value => "b"}, + #{attribute => ?ARC_CLASS_TO_INST, value => "c"}], + ?assertEqual({ok, "b"}, graphdb_instance:find_avp_value(AVPs, ?NAME_ATTR_INSTANCE)). find_avp_value_integer_value_test() -> AVPs = [#{attribute => 5, value => 999}], diff --git a/apps/graphdb/test/graphdb_language_SUITE.erl b/apps/graphdb/test/graphdb_language_SUITE.erl index 23254f7..88c8aff 100644 --- a/apps/graphdb/test/graphdb_language_SUITE.erl +++ b/apps/graphdb/test/graphdb_language_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). -record(node, { nref, @@ -301,10 +302,10 @@ set_labels_writes_avp(_Config) -> {ok, #{lang_code := LCAttr}} = graphdb_language:seeded_nrefs(), %% Write a German label for English nref 10000 DeAVP = #{attribute => LCAttr, value => "Englisch"}, - ok = graphdb_language:set_labels(10000, de, [DeAVP]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, de, [DeAVP]), %% Read it back directly from the Mnesia table [#language_node{avps = AVPs}] = - mnesia:dirty_read(language_de, 10000), + mnesia:dirty_read(language_de, ?NREF_ENGLISH), {value, #{value := "Englisch"}} = lists:search(fun(#{attribute := A}) -> A =:= LCAttr end, AVPs). @@ -315,17 +316,17 @@ set_labels_merges_avps(_Config) -> graphdb_language:seeded_nrefs(), AVP1 = #{attribute => LCAttr, value => "Englisch"}, AVP2 = #{attribute => BLAttr, value => test_sentinel}, - ok = graphdb_language:set_labels(10000, de, [AVP1]), - ok = graphdb_language:set_labels(10000, de, [AVP2]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, de, [AVP1]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, de, [AVP2]), %% Both AVPs present after two writes [#language_node{avps = AVPs}] = - mnesia:dirty_read(language_de, 10000), + mnesia:dirty_read(language_de, ?NREF_ENGLISH), 2 = length(AVPs). set_labels_unregistered_code_error(_Config) -> {ok, _} = graphdb_language:start_link(), {error, unregistered_language} = - graphdb_language:set_labels(10000, xx, []). + graphdb_language:set_labels(?NREF_ENGLISH, xx, []). %%===================================================================== @@ -338,45 +339,45 @@ avp(A, V) -> #{attribute => A, value => V}. resolve_label_from_environment_fallback(_Config) -> {ok, _} = graphdb_language:start_link(), %% Chain [en] → en sentinel → read terminal node directly - %% English nref 10000, instance name AVP attr = 20, value = "English" + %% English nref ?NREF_ENGLISH, instance name AVP attr = 20, value = "English" {ok, "English"} = - graphdb_language:resolve_label(10000, 20, [en], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, ?NAME_ATTR_INSTANCE, [en], environment). resolve_label_from_overlay(_Config) -> {ok, _} = graphdb_language:start_link(), {ok, _} = graphdb_language:register_language(de, "German"), {ok, #{lang_code := LCAttr}} = graphdb_language:seeded_nrefs(), - ok = graphdb_language:set_labels(10000, de, [avp(LCAttr, "Englisch")]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, de, [avp(LCAttr, "Englisch")]), {ok, "Englisch"} = - graphdb_language:resolve_label(10000, LCAttr, [de], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, LCAttr, [de], environment). resolve_label_chain_priority(_Config) -> {ok, _} = graphdb_language:start_link(), {ok, _} = graphdb_language:register_language(de, "German"), {ok, _} = graphdb_language:register_language(fr, "French"), {ok, #{lang_code := LCAttr}} = graphdb_language:seeded_nrefs(), - ok = graphdb_language:set_labels(10000, de, [avp(LCAttr, "Englisch")]), - ok = graphdb_language:set_labels(10000, fr, [avp(LCAttr, "Anglais")]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, de, [avp(LCAttr, "Englisch")]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, fr, [avp(LCAttr, "Anglais")]), %% de appears first — de wins {ok, "Englisch"} = - graphdb_language:resolve_label(10000, LCAttr, [de, fr], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, LCAttr, [de, fr], environment). resolve_label_en_sentinel(_Config) -> {ok, _} = graphdb_language:start_link(), %% Write a wrong value into language_en — sentinel must bypass it - WrongRec = #language_node{nref = 10000, avps = [avp(20, "WRONG")]}, + WrongRec = #language_node{nref = ?NREF_ENGLISH, avps = [avp(?NAME_ATTR_INSTANCE,"WRONG")]}, ok = mnesia:dirty_write(language_en, WrongRec), %% en sentinel skips language_en and reads environment node directly {ok, "English"} = - graphdb_language:resolve_label(10000, 20, [en], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, ?NAME_ATTR_INSTANCE, [en], environment). resolve_label_dialect_hit(_Config) -> {ok, _} = graphdb_language:start_link(), %% en is already bootstrapped at nref 10000; no need to register it {ok, _} = graphdb_language:register_dialect(en_gb, "British English", en), - ok = graphdb_language:set_labels(10000, en_gb, [avp(20, "English (UK)")]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, en_gb, [avp(?NAME_ATTR_INSTANCE,"English (UK)")]), {ok, "English (UK)"} = - graphdb_language:resolve_label(10000, 20, [en_gb, en], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, ?NAME_ATTR_INSTANCE, [en_gb, en], environment). resolve_label_dialect_fallback(_Config) -> %% [en_gb, en, fr]: en_gb miss → en sentinel → environment (fr skipped) @@ -384,16 +385,16 @@ resolve_label_dialect_fallback(_Config) -> %% en is already bootstrapped at nref 10000; no need to register it {ok, _} = graphdb_language:register_dialect(en_gb, "British English", en), {ok, _} = graphdb_language:register_language(fr, "French"), - ok = graphdb_language:set_labels(10000, fr, [avp(20, "Anglais")]), + ok = graphdb_language:set_labels(?NREF_ENGLISH, fr, [avp(?NAME_ATTR_INSTANCE,"Anglais")]), %% en_gb has no overlay for nref 10000 → fall through; en → sentinel → env node {ok, "English"} = - graphdb_language:resolve_label(10000, 20, [en_gb, en, fr], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, ?NAME_ATTR_INSTANCE, [en_gb, en, fr], environment). resolve_label_not_found(_Config) -> {ok, _} = graphdb_language:start_link(), %% AttrNref 99999 does not exist on nref 10000 not_found = - graphdb_language:resolve_label(10000, 99999, [en], environment). + graphdb_language:resolve_label(?NREF_ENGLISH, 99999, [en], environment). %%===================================================================== @@ -428,7 +429,7 @@ project_language_avp_roundtrip(_Config) -> %% Stamp the project_language AVP onto nref 10000 (reuse for simplicity) F = fun() -> [#node{attribute_value_pairs = AVPs} = N] = - mnesia:read(nodes, 10000), + mnesia:read(nodes, ?NREF_ENGLISH), Updated = N#node{ attribute_value_pairs = [#{attribute => PLAttr, value => DeNref} | AVPs] @@ -436,12 +437,12 @@ project_language_avp_roundtrip(_Config) -> mnesia:write(nodes, Updated, write) end, {atomic, ok} = mnesia:transaction(F), - {ok, de} = graphdb_language:project_language(10000). + {ok, de} = graphdb_language:project_language(?NREF_ENGLISH). project_language_not_found(_Config) -> {ok, _} = graphdb_language:start_link(), %% Nref 10000 has no project_language AVP yet - not_found = graphdb_language:project_language(10000). + not_found = graphdb_language:project_language(?NREF_ENGLISH). %%===================================================================== @@ -453,7 +454,7 @@ translation_hook_called_after_registration(_Config) -> Self = self(), Hook = fun(Nref, AVPs) -> Self ! {hook_fired, Nref, AVPs} end, ok = graphdb_language:register_translation_hook(Hook), - graphdb_language:fire_translation_hooks(99, [#{attribute => 20, value => "Test"}]), + graphdb_language:fire_translation_hooks(99, [#{attribute => ?NAME_ATTR_INSTANCE, value => "Test"}]), receive {hook_fired, 99, _AVPs} -> ok after 1000 -> diff --git a/apps/graphdb/test/graphdb_mgr_SUITE.erl b/apps/graphdb/test/graphdb_mgr_SUITE.erl index 69dcea4..d44747b 100644 --- a/apps/graphdb/test/graphdb_mgr_SUITE.erl +++ b/apps/graphdb/test/graphdb_mgr_SUITE.erl @@ -15,6 +15,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). %%--------------------------------------------------------------------- @@ -348,7 +349,7 @@ get_node_root(_Config) -> ?assertEqual(1, Root#node.nref), ?assertEqual(category, Root#node.kind), ?assertEqual([], Root#node.parents), - ?assertEqual([#{attribute => 17, value => "Root"}], + ?assertEqual([#{attribute => ?NAME_ATTR_CATEGORY, value => "Root"}], Root#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -356,11 +357,11 @@ get_node_root(_Config) -> %%----------------------------------------------------------------------------- get_node_attribute(_Config) -> {ok, _} = graphdb_mgr:start_link(), - {ok, Node} = graphdb_mgr:get_node(18), - ?assertEqual(18, Node#node.nref), + {ok, Node} = graphdb_mgr:get_node(?NAME_ATTR_ATTRIBUTE), + ?assertEqual(?NAME_ATTR_ATTRIBUTE, Node#node.nref), ?assertEqual(attribute, Node#node.kind), ?assertEqual([10], Node#node.parents), %% parent: Attribute Name Attributes - ?assertEqual([#{attribute => 18, value => "Name"}], + ?assertEqual([#{attribute => ?NAME_ATTR_ATTRIBUTE, value => "Name"}], Node#node.attribute_value_pairs). %%----------------------------------------------------------------------------- @@ -381,7 +382,7 @@ get_relationships_outgoing(_Config) -> ?assertEqual([2, 3, 4, 5], Targets), %% All should have characterization=22 (Child/CatRel) ?assert(lists:all(fun(R) -> - R#relationship.characterization =:= 22 + R#relationship.characterization =:= ?ARC_CAT_CHILD end, Rels)). %%----------------------------------------------------------------------------- @@ -632,7 +633,7 @@ rebuild_caches_restores_after_poison(_Config) -> {atomic, ok} = mnesia:transaction(fun() -> [N6] = mnesia:read(nodes, 6), [N7] = mnesia:read(nodes, 7), - [N18] = mnesia:read(nodes, 18), + [N18] = mnesia:read(nodes, ?NAME_ATTR_ATTRIBUTE), mnesia:write(nodes, N6#node{parents = [9999]}, write), mnesia:write(nodes, N7#node{classes = [42]}, write), mnesia:write(nodes, N18#node{parents = []}, write) @@ -642,7 +643,7 @@ rebuild_caches_restores_after_poison(_Config) -> ok = graphdb_mgr:rebuild_caches(), ?assertEqual(ok, graphdb_mgr:verify_caches()), {atomic, [Restored18]} = mnesia:transaction(fun() -> - mnesia:read(nodes, 18) + mnesia:read(nodes, ?NAME_ATTR_ATTRIBUTE) end), ?assertEqual([10], Restored18#node.parents). From 10fe5a9f3a3d57191ee50d5aad452fcd8729c874 Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 06:04:49 -0400 Subject: [PATCH 7/8] Task 7: mark scaffold nref macros plan RESOLVED in TASKS.md Co-Authored-By: Claude Sonnet 4.6 --- TASKS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/TASKS.md b/TASKS.md index 78e541a..147a6a7 100644 --- a/TASKS.md +++ b/TASKS.md @@ -834,6 +834,21 @@ in `dictionary_imp:delete/2` (wrong ETS key type). 14 CT tests added (7 per serv --- +### Task 8. Scaffold nref constants → shared `graphdb_nrefs.hrl` header — **RESOLVED** (2026-05-20) + +`apps/graphdb/include/graphdb_nrefs.hrl` introduced with 36 named macros covering +scaffold nrefs 1–35 (`NREF_*`, `NAME_ATTR_*`, `ARC_*`) and the permanent English +instance nref 10000 (`NREF_ENGLISH`). All inline `-define` blocks removed from five +source files (`graphdb_attr`, `graphdb_class`, `graphdb_instance`, `graphdb_language`, +`graphdb_mgr`); all raw integers 17–35 and 10000 replaced with macros in seven test +files. Companion `graphdb_nrefs.erl` exports `scaffold_spec/0` and `verify/0`; verify +is called at the end of `graphdb_bootstrap:do_load/0` as a fatal congruency check. +`graphdb_bootstrap` module is deleted+purged from the code server in `graphdb_mgr:init/1` +after successful load. 2 CT tests in `graphdb_nrefs_SUITE`. 320 tests (217 CT + +103 EUnit), all green, zero warnings. + +--- + ### E2. Non-normal OTP start types **Evidence:** `seerstone:start/2` and `nref:start/2` both hit `?NYI` From 569f3615567cbea21af96ea8437adf80446c1e68 Mon Sep 17 00:00:00 2001 From: "David W. Thomas" Date: Wed, 20 May 2026 06:32:12 -0400 Subject: [PATCH 8/8] design plan used for using macro's for the bootstrap nrefs. --- .../plans/2026-05-20-scaffold-nref-macros.md | 746 ++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-20-scaffold-nref-macros.md diff --git a/docs/superpowers/plans/2026-05-20-scaffold-nref-macros.md b/docs/superpowers/plans/2026-05-20-scaffold-nref-macros.md new file mode 100644 index 0000000..10a14a4 --- /dev/null +++ b/docs/superpowers/plans/2026-05-20-scaffold-nref-macros.md @@ -0,0 +1,746 @@ +# Scaffold Nref Macros Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace all hardcoded scaffold nref integers across graphdb source and test files with named macros from a shared header, add a companion module that verifies macro-to-database congruency at bootstrap end, and unload the bootstrap module once startup is complete. + +**Architecture:** A single header file (`graphdb_nrefs.hrl`) is the authoritative compile-time catalog of all 35 scaffold nrefs plus the English permanent seed. A companion module (`graphdb_nrefs.erl`) exposes an iterable `scaffold_spec/0` used both by `verify/0` (called at the end of bootstrap) and by CT tests. Source modules drop their per-module inline `-define` macros and include the shared header instead. Test files replace raw integer literals with the same macros. + +**Tech Stack:** Erlang/OTP 27, rebar3 3.24, Mnesia (for bootstrap verify), Common Test + +--- + +## File Map + +| Action | Path | Role | +|---------|-------------------------------------------------------------|---------------------------------------------------| +| Create | `apps/graphdb/include/graphdb_nrefs.hrl` | Compile-time macro catalog (36 nrefs) | +| Create | `apps/graphdb/src/graphdb_nrefs.erl` | Runtime iterable spec + `verify/0` | +| Create | `apps/graphdb/test/graphdb_nrefs_SUITE.erl` | CT tests for `verify/0` and bootstrap unloading | +| Modify | `apps/graphdb/src/graphdb_bootstrap.erl` | Call `graphdb_nrefs:verify/0` at end of `do_load` | +| Modify | `apps/graphdb/src/graphdb_mgr.erl` | Unload bootstrap after successful init | +| Modify | `apps/graphdb/src/graphdb_attr.erl` | Drop inline defines, include header | +| Modify | `apps/graphdb/src/graphdb_class.erl` | Drop inline defines, include header | +| Modify | `apps/graphdb/src/graphdb_instance.erl` | Drop inline defines, include header | +| Modify | `apps/graphdb/src/graphdb_language.erl` | Drop inline defines, include header | +| Modify | `apps/graphdb/test/graphdb_bootstrap_SUITE.erl` | Replace raw integers with macros | +| Modify | `apps/graphdb/test/graphdb_attr_SUITE.erl` | Replace raw integers with macros | +| Modify | `apps/graphdb/test/graphdb_class_SUITE.erl` | Replace raw integers with macros | +| Modify | `apps/graphdb/test/graphdb_instance_SUITE.erl` | Replace raw integers with macros | +| Modify | `apps/graphdb/test/graphdb_language_SUITE.erl` | Replace raw integers and 10000 with macros | +| Modify | `apps/graphdb/test/graphdb_instance_tests.erl` | Replace raw integers with macros | +| Modify | `apps/graphdb/test/graphdb_mgr_SUITE.erl` | Replace inline defines; add bootstrap-unload test | + +--- + +### Task 1: Create `graphdb_nrefs.hrl` + +**Files:** +- Create: `apps/graphdb/include/graphdb_nrefs.hrl` + +- [ ] **Step 1: Create the header file** + +```erlang +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +%% graphdb_nrefs.hrl -- Compile-time names for immutable scaffold nrefs. +%% +%% Scaffold nrefs 1-35 are written once at bootstrap and never reallocated. +%% Changing any value here requires re-bootstrapping the environment. +%% All values correspond directly to bootstrap.terms entries. +%% +%% nref_english (10000) is a permanent seed in the 10000-99999 tier. +%%--------------------------------------------------------------------- + +%% -- Top-level categories (scaffold 1-5) ------------------------------ +-define(NREF_ROOT, 1). +-define(NREF_ATTRIBUTES, 2). +-define(NREF_CLASSES, 3). +-define(NREF_LANGUAGES, 4). +-define(NREF_PROJECTS, 5). + +%% -- Attribute family roots (scaffold 6-8) ---------------------------- +-define(NREF_NAMES, 6). +-define(NREF_LITERALS, 7). +-define(NREF_RELATIONSHIPS, 8). + +%% -- Name-attribute subcategory nodes (scaffold 9-12) ----------------- +-define(NREF_CAT_NAME_ATTRS, 9). +-define(NREF_ATTR_NAME_ATTRS, 10). +-define(NREF_CLS_NAME_ATTRS, 11). +-define(NREF_INST_NAME_ATTRS, 12). + +%% -- Relationship-attribute subcategory nodes (scaffold 13-16) -------- +-define(NREF_CAT_REL_ATTRS, 13). +-define(NREF_ATTR_REL_ATTRS, 14). +-define(NREF_CLS_REL_ATTRS, 15). +-define(NREF_INST_REL_ATTRS, 16). + +%% -- Name attributes: used as #{attribute => ?NAME_ATTR_*, value => Name} +-define(NAME_ATTR_CATEGORY, 17). +-define(NAME_ATTR_ATTRIBUTE, 18). +-define(NAME_ATTR_CLASS, 19). +-define(NAME_ATTR_INSTANCE, 20). + +%% -- Category hierarchy arc labels (kind = composition) --------------- +-define(ARC_CAT_PARENT, 21). +-define(ARC_CAT_CHILD, 22). + +%% -- Attribute hierarchy arc labels (kind = taxonomy) ----------------- +-define(ARC_ATTR_PARENT, 23). +-define(ARC_ATTR_CHILD, 24). + +%% -- Class hierarchy arc labels (kind = taxonomy or composition) ------ +-define(ARC_CLS_PARENT, 25). +-define(ARC_CLS_CHILD, 26). + +%% -- Instance hierarchy arc labels (kind = composition) --------------- +-define(ARC_INST_PARENT, 27). +-define(ARC_INST_CHILD, 28). + +%% -- Instance-class membership arc labels ----------------------------- +-define(ARC_INST_TO_CLASS, 29). %% instance -> class direction +-define(ARC_CLASS_TO_INST, 30). %% class -> instance direction + +%% -- Template scope AVP marker ---------------------------------------- +-define(ARC_TEMPLATE, 31). + +%% -- Language subcategories (scaffold 32-35) -------------------------- +-define(NREF_HUMAN_LANGS, 32). +-define(NREF_FORMAL_LANGS, 33). +-define(NREF_DIAGRAM_LANGS, 34). +-define(NREF_RENDERERS, 35). + +%% -- Permanent named instance nrefs (10000-99999 tier) ---------------- +-define(NREF_ENGLISH, 10000). %% English; first instance in ontology +``` + +- [ ] **Step 2: Verify the header compiles** + +Add `-include_lib("graphdb/include/graphdb_nrefs.hrl").` to `apps/graphdb/src/graphdb_bootstrap.erl` (it will use it in Task 3). Run: + +```sh +./rebar3 compile +``` + +Expected: zero warnings, zero errors. + +- [ ] **Step 3: Commit** + +```sh +git add apps/graphdb/include/graphdb_nrefs.hrl apps/graphdb/src/graphdb_bootstrap.erl +git commit -m "feat: add graphdb_nrefs.hrl scaffold nref macro catalog" +``` + +--- + +### Task 2: Create `graphdb_nrefs.erl` with `scaffold_spec/0` and `verify/0` + +**Files:** +- Create: `apps/graphdb/src/graphdb_nrefs.erl` +- Create: `apps/graphdb/test/graphdb_nrefs_SUITE.erl` + +- [ ] **Step 1: Write the failing CT test** + +Create `apps/graphdb/test/graphdb_nrefs_SUITE.erl`: + +```erlang +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +-module(graphdb_nrefs_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("graphdb/include/graphdb_nrefs.hrl"). + +-export([all/0, groups/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). + +-export([verify_returns_ok/1, + bootstrap_module_unloaded/1]). + +-define(SCRATCH_SENTINEL, "_build/test/ct_scratch/"). +-define(DIR_PREFIX, "nrefs_"). + +all() -> [{group, congruency}]. + +groups() -> + [{congruency, [sequence], [ + verify_returns_ok, + bootstrap_module_unloaded + ]}]. + +init_per_suite(Config) -> + {ok, OrigCwd} = file:get_cwd(), + [{orig_cwd, OrigCwd} | Config]. + +end_per_suite(_Config) -> ok. + +init_per_testcase(_TC, Config) -> + OrigCwd = proplists:get_value(orig_cwd, Config), + Unique = integer_to_list(erlang:unique_integer([positive, monotonic])), + TmpDir = filename:join([OrigCwd, "_build", "test", "ct_scratch", + ?DIR_PREFIX ++ Unique]), + ok = filelib:ensure_dir(filename:join(TmpDir, "x")), + ok = file:set_cwd(TmpDir), + case application:load(nref) of + ok -> ok; + {error, {already_loaded, nref}} -> ok + end, + case application:load(graphdb) of + ok -> ok; + {error, {already_loaded, graphdb}} -> ok + end, + ok = application:set_env(seerstone_graph_db, data_path, TmpDir), + ok = application:set_env(seerstone_graph_db, bootstrap_file, + filename:join(code:priv_dir(graphdb), "bootstrap.terms")), + ok = application:set_env(mnesia, dir, TmpDir), + {ok, _} = rel_id_server:start_link(), + {ok, _} = graphdb_mgr:start_link(), + [{tmp_dir, TmpDir} | Config]. + +end_per_testcase(_TC, Config) -> + catch gen_server:stop(graphdb_mgr), + catch gen_server:stop(graphdb_attr), + catch gen_server:stop(graphdb_class), + catch gen_server:stop(graphdb_instance), + catch gen_server:stop(graphdb_language), + catch gen_server:stop(rel_id_server), + catch dets:close(rel_id_server), + catch application:stop(nref), + catch dets:close(nref_allocator), + catch dets:close(nref_server), + catch mnesia:stop(), + application:unset_env(seerstone_graph_db, data_path), + application:unset_env(seerstone_graph_db, bootstrap_file), + application:unset_env(mnesia, dir), + OrigCwd = proplists:get_value(orig_cwd, Config), + ok = file:set_cwd(OrigCwd), + TmpDir = proplists:get_value(tmp_dir, Config), + delete_dir_recursive(TmpDir), + ok. + +%%============================================================================= +%% Test Cases +%%============================================================================= + +verify_returns_ok(_Config) -> + %% After a successful bootstrap, every macro value must match + %% its corresponding Mnesia node. + ?assertEqual(ok, graphdb_nrefs:verify()). + +bootstrap_module_unloaded(_Config) -> + %% graphdb_mgr:init/1 unloads graphdb_bootstrap after a successful load. + %% code:is_loaded/1 returns false when a module is not in the code server. + ?assertEqual(false, code:is_loaded(graphdb_bootstrap)). + +%%============================================================================= +%% Helpers +%%============================================================================= + +delete_dir_recursive(Dir) -> + case is_safe_scratch_dir(Dir) of + true -> do_delete_dir(Dir); + false -> error({unsafe_delete, Dir}) + end. + +is_safe_scratch_dir(Dir) -> + Abs = filename:absname(Dir), + IsAbsolute = (Abs =:= Dir), + ContainsSentinel = (string:find(Dir, ?SCRATCH_SENTINEL) =/= nomatch), + Leaf = filename:basename(Dir), + HasPrefix = lists:prefix(?DIR_PREFIX, Leaf), + IsAbsolute andalso ContainsSentinel andalso HasPrefix. + +do_delete_dir(Dir) -> + {ok, Entries} = file:list_dir(Dir), + lists:foreach(fun(E) -> + Path = filename:join(Dir, E), + case filelib:is_dir(Path) of + false -> file:delete(Path); + true -> do_delete_dir(Path) + end + end, Entries), + file:del_dir(Dir). +``` + +- [ ] **Step 2: Run the test to confirm it fails** + +```sh +./rebar3 ct --suite apps/graphdb/test/graphdb_nrefs_SUITE +``` + +Expected: FAIL — `graphdb_nrefs` module does not exist yet. + +- [ ] **Step 3: Create `graphdb_nrefs.erl`** + +```erlang +%%--------------------------------------------------------------------- +%% Copyright (c) 2026 David W. Thomas +%% SPDX-License-Identifier: GPL-2.0-or-later +%%--------------------------------------------------------------------- +%% Author: David W. Thomas +%% Description: Runtime catalog of scaffold nrefs and bootstrap +%% congruency verification. +%%--------------------------------------------------------------------- + +-module(graphdb_nrefs). + +-include_lib("graphdb/include/graphdb_nrefs.hrl"). + +-record(node, { + nref, + kind, + parents = [], + classes = [], + attribute_value_pairs +}). + +-export([scaffold_spec/0, verify/0]). + + +%%--------------------------------------------------------------------- +%% scaffold_spec() -> [{atom(), integer(), atom(), string()}] +%% +%% Returns {MacroName, Nref, Kind, ExpectedName} for every immutable +%% scaffold nref. Used by verify/0 and CT tests. +%%--------------------------------------------------------------------- +scaffold_spec() -> [ + {nref_root, ?NREF_ROOT, category, "Root"}, + {nref_attributes, ?NREF_ATTRIBUTES, category, "Attributes"}, + {nref_classes, ?NREF_CLASSES, category, "Classes"}, + {nref_languages, ?NREF_LANGUAGES, category, "Languages"}, + {nref_projects, ?NREF_PROJECTS, category, "Projects"}, + {nref_names, ?NREF_NAMES, attribute, "Names"}, + {nref_literals, ?NREF_LITERALS, attribute, "Literals"}, + {nref_relationships, ?NREF_RELATIONSHIPS, attribute, "Relationships"}, + {nref_cat_name_attrs, ?NREF_CAT_NAME_ATTRS, attribute, "Category Name Attributes"}, + {nref_attr_name_attrs,?NREF_ATTR_NAME_ATTRS, attribute, "Attribute Name Attributes"}, + {nref_cls_name_attrs, ?NREF_CLS_NAME_ATTRS, attribute, "Class Name Attributes"}, + {nref_inst_name_attrs,?NREF_INST_NAME_ATTRS, attribute, "Instance Name Attributes"}, + {nref_cat_rel_attrs, ?NREF_CAT_REL_ATTRS, attribute, "Category Relationships"}, + {nref_attr_rel_attrs, ?NREF_ATTR_REL_ATTRS, attribute, "Attribute Relationships"}, + {nref_cls_rel_attrs, ?NREF_CLS_REL_ATTRS, attribute, "Class Relationships"}, + {nref_inst_rel_attrs, ?NREF_INST_REL_ATTRS, attribute, "Instance Relationships"}, + {name_attr_category, ?NAME_ATTR_CATEGORY, attribute, "Name"}, + {name_attr_attribute, ?NAME_ATTR_ATTRIBUTE, attribute, "Name"}, + {name_attr_class, ?NAME_ATTR_CLASS, attribute, "Name"}, + {name_attr_instance, ?NAME_ATTR_INSTANCE, attribute, "Name"}, + {arc_cat_parent, ?ARC_CAT_PARENT, attribute, "Parent"}, + {arc_cat_child, ?ARC_CAT_CHILD, attribute, "Child"}, + {arc_attr_parent, ?ARC_ATTR_PARENT, attribute, "Parent"}, + {arc_attr_child, ?ARC_ATTR_CHILD, attribute, "Child"}, + {arc_cls_parent, ?ARC_CLS_PARENT, attribute, "Parent"}, + {arc_cls_child, ?ARC_CLS_CHILD, attribute, "Child"}, + {arc_inst_parent, ?ARC_INST_PARENT, attribute, "Parent"}, + {arc_inst_child, ?ARC_INST_CHILD, attribute, "Child"}, + {arc_inst_to_class, ?ARC_INST_TO_CLASS, attribute, "Class"}, + {arc_class_to_inst, ?ARC_CLASS_TO_INST, attribute, "Instance"}, + {arc_template, ?ARC_TEMPLATE, attribute, "Template"}, + {nref_human_langs, ?NREF_HUMAN_LANGS, category, "Human Languages"}, + {nref_formal_langs, ?NREF_FORMAL_LANGS, category, "Formal Languages"}, + {nref_diagram_langs, ?NREF_DIAGRAM_LANGS, category, "Diagram Languages"}, + {nref_renderers, ?NREF_RENDERERS, category, "Renderers"}, + {nref_english, ?NREF_ENGLISH, instance, "English"} +]. + + +%%--------------------------------------------------------------------- +%% verify() -> ok | {error, {scaffold_nref_mismatch, [term()]}} +%% +%% Reads every scaffold nref from Mnesia and confirms it has the +%% expected kind and name AVP. Called at the end of bootstrap load. +%%--------------------------------------------------------------------- +verify() -> + Mismatches = lists:flatmap(fun verify_one/1, scaffold_spec()), + case Mismatches of + [] -> ok; + _ -> {error, {scaffold_nref_mismatch, Mismatches}} + end. + +verify_one({Name, Nref, ExpKind, ExpNameValue}) -> + NameAttr = name_attr_for_kind(ExpKind), + case mnesia:dirty_read(nodes, Nref) of + [#node{kind = ExpKind, attribute_value_pairs = AVPs}] -> + HasName = lists:any( + fun(#{attribute := A, value := V}) -> + A =:= NameAttr andalso V =:= ExpNameValue + end, AVPs), + case HasName of + true -> []; + false -> [{Name, Nref, name_not_found, ExpNameValue}] + end; + [#node{kind = ActualKind}] -> + [{Name, Nref, kind_mismatch, ExpKind, ActualKind}]; + [] -> + [{Name, Nref, node_not_found}] + end. + +name_attr_for_kind(category) -> ?NAME_ATTR_CATEGORY; +name_attr_for_kind(attribute) -> ?NAME_ATTR_ATTRIBUTE; +name_attr_for_kind(class) -> ?NAME_ATTR_CLASS; +name_attr_for_kind(instance) -> ?NAME_ATTR_INSTANCE. +``` + +- [ ] **Step 4: Run the test — expect `verify_returns_ok` to pass, `bootstrap_module_unloaded` to fail** + +```sh +./rebar3 ct --suite apps/graphdb/test/graphdb_nrefs_SUITE +``` + +Expected: `verify_returns_ok` PASS (bootstrap already populates Mnesia), `bootstrap_module_unloaded` FAIL — bootstrap is still in the code server until Task 4. + +- [ ] **Step 5: Commit** + +```sh +git add apps/graphdb/src/graphdb_nrefs.erl apps/graphdb/test/graphdb_nrefs_SUITE.erl +git commit -m "feat: add graphdb_nrefs companion module with scaffold_spec/0 and verify/0" +``` + +--- + +### Task 3: Wire `graphdb_nrefs:verify/0` into the bootstrap end sequence + +**Files:** +- Modify: `apps/graphdb/src/graphdb_bootstrap.erl` + +The bootstrap already calls `rebuild_and_verify_caches/0` at the end of `do_load/0`. Add the scaffold verify immediately after. + +- [ ] **Step 1: Find the `rebuild_and_verify_caches` call site in `do_load/0`** + +Run: + +```sh +grep -n "rebuild_and_verify_caches\|do_load" apps/graphdb/src/graphdb_bootstrap.erl +``` + +Note the line number of the `rebuild_and_verify_caches` call inside `do_load`. + +- [ ] **Step 2: Add the `graphdb_nrefs:verify/0` call** + +In `graphdb_bootstrap.erl`, inside `do_load/0`, after the `rebuild_and_verify_caches()` call, add: + +```erlang +ok = graphdb_nrefs:verify(), +``` + +The `verify/0` call must come after nodes and caches are fully written. If it returns `{error, _}`, the `= ok` match causes `do_load` to throw, which is the desired fatal-startup behavior — same semantics as the cache invariant check. + +- [ ] **Step 3: Compile and run the full bootstrap test suite** + +```sh +./rebar3 compile && ./rebar3 ct --suite apps/graphdb/test/graphdb_bootstrap_SUITE +``` + +Expected: all bootstrap tests pass (the verify call succeeds silently; existing tests already exercise the post-load state). + +- [ ] **Step 4: Commit** + +```sh +git add apps/graphdb/src/graphdb_bootstrap.erl +git commit -m "feat: call graphdb_nrefs:verify/0 at end of bootstrap load" +``` + +--- + +### Task 4: Unload the bootstrap module after successful startup + +**Files:** +- Modify: `apps/graphdb/src/graphdb_mgr.erl` + +- [ ] **Step 1: Locate the `graphdb_bootstrap:load()` call in `init/1`** + +```sh +grep -n "graphdb_bootstrap:load\|code:delete\|code:purge" apps/graphdb/src/graphdb_mgr.erl +``` + +- [ ] **Step 2: Add the unload sequence after a successful load** + +In `graphdb_mgr:init/1`, the successful branch currently reads: + +```erlang +ok -> + logger:info("graphdb_mgr: started, bootstrap loaded"), + {ok, initial_state()} +``` + +Change it to: + +```erlang +ok -> + code:delete(graphdb_bootstrap), + code:purge(graphdb_bootstrap), + logger:info("graphdb_mgr: started, bootstrap loaded"), + {ok, initial_state()} +``` + +`code:delete` marks the module as old. `code:purge` removes old code (safe immediately since `graphdb_bootstrap:load/0` has already returned and no process is executing inside it). Subsequent calls to `graphdb_bootstrap:` in test cases auto-reload the module from the code path — no test setup changes required. + +- [ ] **Step 3: Run the `graphdb_nrefs_SUITE` to confirm both tests pass** + +```sh +./rebar3 ct --suite apps/graphdb/test/graphdb_nrefs_SUITE +``` + +Expected: both `verify_returns_ok` and `bootstrap_module_unloaded` PASS. + +- [ ] **Step 4: Run the full graphdb CT suite to confirm no regressions** + +```sh +./rebar3 ct --dir apps/graphdb/test +``` + +Expected: all graphdb CT tests pass. + +- [ ] **Step 5: Commit** + +```sh +git add apps/graphdb/src/graphdb_mgr.erl +git commit -m "feat: unload graphdb_bootstrap from code server after startup" +``` + +--- + +### Task 5: Migrate graphdb source files to use the shared header + +**Files:** +- Modify: `apps/graphdb/src/graphdb_attr.erl` +- Modify: `apps/graphdb/src/graphdb_class.erl` +- Modify: `apps/graphdb/src/graphdb_instance.erl` +- Modify: `apps/graphdb/src/graphdb_language.erl` +- Modify: `apps/graphdb/src/graphdb_mgr.erl` + +Each source module currently defines its own inline `-define` macros for the scaffold nrefs it uses. The migration is: add the `include_lib`, remove the inline defines, and rename usages to the standard names. + +- [ ] **Step 1: Migrate `graphdb_attr.erl`** + +Add after the `?NYI`/`?UEM` macro block: + +```erlang +-include_lib("graphdb/include/graphdb_nrefs.hrl"). +``` + +Remove these inline defines (around lines 90-101): + +```erlang +-define(NAME_ATTR_FOR_ATTRIBUTE, 18). +-define(ATTR_CHILD_ARC, 24). +-define(ATTR_PARENT_ARC, 23). +-define(TEMPLATE_AVP_NREF, 31). +``` + +Replace all usages in the module: + +| Old macro | New macro | +|-------------------------|------------------------| +| `?NAME_ATTR_FOR_ATTRIBUTE` | `?NAME_ATTR_ATTRIBUTE` | +| `?ATTR_CHILD_ARC` | `?ARC_ATTR_CHILD` | +| `?ATTR_PARENT_ARC` | `?ARC_ATTR_PARENT` | +| `?TEMPLATE_AVP_NREF` | `?ARC_TEMPLATE` | + +- [ ] **Step 2: Migrate `graphdb_class.erl`** + +Add `-include_lib("graphdb/include/graphdb_nrefs.hrl").` + +Remove inline defines (around lines 78-82): + +```erlang +-define(NAME_ATTR_FOR_CLASS, 19). +-define(CLASS_CHILD_ARC, 26). +-define(CLASS_PARENT_ARC, 25). +``` + +Replace all usages: + +| Old macro | New macro | +|----------------------|--------------------| +| `?NAME_ATTR_FOR_CLASS` | `?NAME_ATTR_CLASS` | +| `?CLASS_CHILD_ARC` | `?ARC_CLS_CHILD` | +| `?CLASS_PARENT_ARC` | `?ARC_CLS_PARENT` | + +- [ ] **Step 3: Migrate `graphdb_instance.erl`** + +Add `-include_lib("graphdb/include/graphdb_nrefs.hrl").` + +Remove inline defines (around lines 78-91): + +```erlang +-define(NAME_ATTR_FOR_INSTANCE, 20). +-define(INST_CHILD_ARC, 28). +-define(INST_PARENT_ARC, 27). +-define(CLASS_MEMBERSHIP_ARC, 29). +-define(INSTANCE_MEMBERSHIP_ARC, 30). +-define(TEMPLATE_AVP_NREF, 31). +``` + +Replace all usages: + +| Old macro | New macro | +|----------------------------|------------------------| +| `?NAME_ATTR_FOR_INSTANCE` | `?NAME_ATTR_INSTANCE` | +| `?INST_CHILD_ARC` | `?ARC_INST_CHILD` | +| `?INST_PARENT_ARC` | `?ARC_INST_PARENT` | +| `?CLASS_MEMBERSHIP_ARC` | `?ARC_INST_TO_CLASS` | +| `?INSTANCE_MEMBERSHIP_ARC` | `?ARC_CLASS_TO_INST` | +| `?TEMPLATE_AVP_NREF` | `?ARC_TEMPLATE` | + +- [ ] **Step 4: Migrate `graphdb_language.erl`** + +Add `-include_lib("graphdb/include/graphdb_nrefs.hrl").` + +Remove inline defines (around lines 59-69): + +```erlang +-define(HUMAN_LANGS, 32). +-define(NAME_ATTR_FOR_ATTRIBUTE, 18). +-define(NAME_ATTR_FOR_CLASS, 19). +-define(NAME_ATTR_FOR_INSTANCE, 20). +-define(ATTR_PARENT_ARC, 23). +-define(ATTR_CHILD_ARC, 24). +-define(CLASS_CHILD_ARC, 26). +-define(INST_PARENT_ARC, 27). +-define(INST_CHILD_ARC, 28). +-define(CLASS_MEMBERSHIP_ARC, 29). +-define(INSTANCE_MEMBERSHIP_ARC, 30). +``` + +Replace all usages: + +| Old macro | New macro | +|----------------------------|------------------------| +| `?HUMAN_LANGS` | `?NREF_HUMAN_LANGS` | +| `?NAME_ATTR_FOR_ATTRIBUTE` | `?NAME_ATTR_ATTRIBUTE` | +| `?NAME_ATTR_FOR_CLASS` | `?NAME_ATTR_CLASS` | +| `?NAME_ATTR_FOR_INSTANCE` | `?NAME_ATTR_INSTANCE` | +| `?ATTR_PARENT_ARC` | `?ARC_ATTR_PARENT` | +| `?ATTR_CHILD_ARC` | `?ARC_ATTR_CHILD` | +| `?CLASS_CHILD_ARC` | `?ARC_CLS_CHILD` | +| `?INST_PARENT_ARC` | `?ARC_INST_PARENT` | +| `?INST_CHILD_ARC` | `?ARC_INST_CHILD` | +| `?CLASS_MEMBERSHIP_ARC` | `?ARC_INST_TO_CLASS` | +| `?INSTANCE_MEMBERSHIP_ARC` | `?ARC_CLASS_TO_INST` | + +- [ ] **Step 5: Migrate `graphdb_mgr.erl`** + +Add `-include_lib("graphdb/include/graphdb_nrefs.hrl").` + +Replace the two inline defines (around lines 483-484). These are list/alias defines that reference multiple nrefs — keep the local aliases but update their values to use the shared macros: + +```erlang +%% Before +-define(PARENT_ARCS, [21, 23, 25, 27]). +-define(CLASS_MEMBERSHIP_ARC, 29). + +%% After +-define(PARENT_ARCS, [?ARC_CAT_PARENT, ?ARC_ATTR_PARENT, + ?ARC_CLS_PARENT, ?ARC_INST_PARENT]). +-define(CLASS_MEMBERSHIP_ARC, ?ARC_INST_TO_CLASS). +``` + +- [ ] **Step 6: Compile — zero warnings** + +```sh +./rebar3 compile +``` + +Expected: zero warnings, zero errors. Any `unused macro` warning means a usage site was missed. + +- [ ] **Step 7: Run the full graphdb CT suite** + +```sh +./rebar3 ct --dir apps/graphdb/test +``` + +Expected: all tests pass. + +- [ ] **Step 8: Commit** + +```sh +git add apps/graphdb/src/graphdb_attr.erl apps/graphdb/src/graphdb_class.erl \ + apps/graphdb/src/graphdb_instance.erl apps/graphdb/src/graphdb_language.erl \ + apps/graphdb/src/graphdb_mgr.erl +git commit -m "refactor: replace per-module scaffold nref defines with shared graphdb_nrefs.hrl" +``` + +--- + +### Task 6: Migrate graphdb test files to use the shared header + +**Files:** +- Modify: `apps/graphdb/test/graphdb_bootstrap_SUITE.erl` +- Modify: `apps/graphdb/test/graphdb_attr_SUITE.erl` +- Modify: `apps/graphdb/test/graphdb_class_SUITE.erl` +- Modify: `apps/graphdb/test/graphdb_instance_SUITE.erl` +- Modify: `apps/graphdb/test/graphdb_language_SUITE.erl` +- Modify: `apps/graphdb/test/graphdb_instance_tests.erl` +- Modify: `apps/graphdb/test/graphdb_mgr_SUITE.erl` + +Test files use raw integer literals directly (no inline defines). The migration is: add the `include_lib` line and replace every `17`, `18`, `19`, `20`, `21`–`31`, `32`–`35`, and `10000` that refers to a scaffold nref with the corresponding macro. + +For each file in the list below, follow this pattern: + +``` +1. Add -include_lib("graphdb/include/graphdb_nrefs.hrl"). near the top + (after -include_lib("common_test/...") or eunit lines) +2. Replace integer literals with macros using the table from Task 5. +3. Replace 10000 with ?NREF_ENGLISH (language_SUITE only — ~20 sites). +``` + +- [ ] **Step 1: Migrate all seven test files** (do them together — they are mechanical substitutions) + +Key pattern: `#{attribute => 17, ...}` → `#{attribute => ?NAME_ATTR_CATEGORY, ...}`. + +In `graphdb_language_SUITE.erl`, replace every occurrence of `10000` that refers to the English node nref with `?NREF_ENGLISH`. Do NOT replace `10000` where it appears in comments describing a floor assertion — those are not nref references. + +In `graphdb_instance_tests.erl`, replace `#{attribute => 10, value => ...}` with `#{attribute => ?NREF_ATTR_NAME_ATTRS, value => ...}` and other scaffold integers similarly. + +- [ ] **Step 2: Compile — zero warnings** + +```sh +./rebar3 compile +``` + +- [ ] **Step 3: Run the full test suite** + +```sh +./rebar3 ct --dir apps/graphdb/test && ./rebar3 eunit --app=graphdb +``` + +Expected: all CT and EUnit tests pass. + +- [ ] **Step 4: Commit** + +```sh +git add apps/graphdb/test/ +git commit -m "refactor: replace raw scaffold nref integers with macros in test files" +``` + +--- + +### Task 7: Update TASKS.md and wrap up + +**Files:** +- Modify: `TASKS.md` + +- [ ] **Step 1: Run the complete test suite one final time** + +```sh +./rebar3 compile && ./rebar3 ct --dir apps/graphdb/test && ./rebar3 eunit --app=graphdb +``` + +Expected: zero warnings; all CT and EUnit tests pass. + +- [ ] **Step 2: Mark the task RESOLVED in TASKS.md** + +Add a RESOLVED note to the scaffold nref macros entry. + +- [ ] **Step 3: Commit** + +```sh +git add TASKS.md +git commit -m "docs: mark scaffold nref macros task RESOLVED" +```