Skip to content
15 changes: 15 additions & 0 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
74 changes: 74 additions & 0 deletions apps/graphdb/include/graphdb_nrefs.hrl
Original file line number Diff line number Diff line change
@@ -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
94 changes: 36 additions & 58 deletions apps/graphdb/src/graphdb_attr.erl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
%%---------------------------------------------------------------------
%% Include files
%%---------------------------------------------------------------------
-include_lib("graphdb/include/graphdb_nrefs.hrl").

%%---------------------------------------------------------------------
%% Macro Functions
Expand All @@ -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
%%---------------------------------------------------------------------
Expand Down Expand Up @@ -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},
Expand All @@ -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};

%%-----------------------------------------------------------------------------
Expand All @@ -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) ->
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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).

Expand All @@ -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,
Expand All @@ -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() ->
Expand Down Expand Up @@ -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() ->
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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 ->
Expand All @@ -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;
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions apps/graphdb/src/graphdb_bootstrap.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
end)).


%%---------------------------------------------------------------------
%% Include Files
%%---------------------------------------------------------------------
-include_lib("graphdb/include/graphdb_nrefs.hrl").

%%---------------------------------------------------------------------
%% Record Definitions
%%---------------------------------------------------------------------
Expand Down Expand Up @@ -240,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;
Expand Down
Loading
Loading