H0 cache invariant + H1/H2 inheritance fixes#10
Merged
david-w-t merged 7 commits intodavidwt-com:mainfrom May 8, 2026
Merged
Conversation
Opens the next series of work. C1, C2, and C3 landed in PR davidwt-com#9 (`ce2e281`); the task file's job is done. Per the task-file retirement workflow, this commit removes the file and updates every doc that enumerated it: project root `README.md`, `CLAUDE.md`, `ARCHITECTURE.md`, plus `apps/database/CLAUDE.md` and `apps/graphdb/CLAUDE.md`. The remaining task files are now `TASKS-HIGH.md`, `TASKS-MEDIUM.md`, and `TASKS-LOW.md`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
H1 (graphdb_instance:resolve_from_class)
Stop reading exactly one class node's AVPs. Locate the membership
arc via the existing do_class_of/1, then ask graphdb_class for the
class node and ancestor chain and return the first AVP match.
Subsumes M2 (resolve_from_class no longer reads the nodes table
directly or hard-codes ?CLASS_MEMBERSHIP_ARC inside the resolver).
H2 (graphdb_instance:resolve_from_connected)
Filter the outgoing relationships to kind = connection before
pulling target nrefs. Instantiation (membership, char=29) and
composition (parent/child, char=27/28) arcs no longer feed
Priority 4; those targets are already covered by Priorities 2 and 3.
Tests
Three new CT cases in graphdb_instance_SUITE:
- resolve_value_walks_class_taxonomy
- resolve_value_local_class_overrides_taxonomy_ancestor
- resolve_value_p4_ignores_compositional_arc
Full suite: 118 CT + 64 EUnit = 182, all green.
Docs
TASKS-HIGH.md: H1 and H2 marked RESOLVED with status notes; intro
rewritten to reflect that only H3-H5 remain.
TASKS-MEDIUM.md: M2 marked RESOLVED (closed by H1).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the H0 decision record (arcs-authoritative.md) and an H0 entry
to TASKS-HIGH.md with substep checklist (H0a-H0e). No code changes.
Decision in summary:
- Arcs are the sole authoritative source for hierarchy (taxonomy,
composition, instantiation).
- node.parents :: [integer()] and node.classes :: [integer()] are
caches reconstructable from the arcs. node.parent (singular) is
retired.
- Each cache field has exactly one owner worker; arc and cache
updates happen in one mnesia:transaction/1.
- Bootstrap follows Option B: arcs only in the file, with %%
comments per relationship; loader rebuilds and verifies caches
after writing arcs.
- Cache/arc disagreement is a fatal error.
H0 lands before H3 so the multi-parent class case (H3) and
multi-class instance case (H4) build on a single uniform pattern
rather than schema migrations tangled with semantic changes.
H0 closes M1 ("PART-OF stored in two places with no consistency
invariant") on completion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Schema change for the "arcs authoritative; hierarchy lists cached"
invariant (charter: arcs-authoritative.md, plan: TASKS-HIGH.md H0b).
Record change in `node`:
- retire `parent :: integer() | undefined`
- add `parents = [] :: [integer()]` -- cache of compositional /
taxonomic parent arcs
- add `classes = [] :: [integer()]` -- cache of instantiation arcs
(instances only)
Bootstrap loader (graphdb_bootstrap):
- term_to_node converts the legacy single Parent integer into
parents=[Parent] (or [] for the Root node) and seeds classes=[]
- drop {index, [parent]} from create_tables; downward lookups now
go through the relationships table
- bootstrap.terms format unchanged (Option B switch lands in H0d)
Write paths populate caches transactionally:
- graphdb_attr:do_create_attribute / do_create_seed_attribute
- graphdb_class:do_create_class (class + default template),
do_write_template, do_create_seed_attribute
- graphdb_instance:do_write_instance (parents + classes)
Read paths replace the retired parent index:
- downward "list children" lookups switch to a private
downward_children_by_arc/3 helper that reads outgoing arcs from
the relationships table filtered by Kind + characterization
- graphdb_attr: find_attribute_by_name, do_list_children (char=24)
- graphdb_class: find_attribute_by_name, do_find_template_by_name,
do_templates_for_class (char=26 composition), do_subclasses
(char=26 taxonomy)
- graphdb_instance: do_children (char=28 composition)
Single-chain ancestor walks read parents=[P|_] via head_parent/1
helper; multi-parent traversal arrives in H3. validate_template_scope
and resolve_from_ancestors switch to the same helper.
Tests: 64 EUnit + 118 CT = 182 all green. Caches populated as
length-1 lists; single-parent semantics preserved per H0b spec.
graphdb_bootstrap_SUITE:load_category_children rewritten to verify
children via composition arcs (the parent index is gone).
graphdb_bootstrap_tests element/_ position assertions updated for
the new {node, Nref, Kind, Parents, Classes, AVPs} tuple shape.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the cache-invariant audit/repair APIs spec'd in
arcs-authoritative.md and wires them into every CT suite.
graphdb_mgr exports two new APIs:
- verify_caches/0 -> ok | {error, [{Nref, Field, Expected, Actual}]}
Scans every node, compares parents/classes against the arcs.
Order-insensitive; returns the full mismatch list when wrong.
- rebuild_caches/0 -> ok | {error, term()}
Rewrites every node's parents/classes from the arcs in one txn.
Used as a repair tool and (after H0d) as the bootstrap loader's
post-load tail.
Internal helpers:
- expected_parents/1 reads outgoing arcs (kind in {composition,
taxonomy}) whose characterization is one of the parent labels
{21, 23, 25, 27} and returns the target nrefs.
- expected_classes/1 reads outgoing instantiation arcs (char=29)
and returns the target class nrefs (empty for non-instances).
- verify_one/1 / rebuild_one/1 -- per-node primitives.
CT enforcement -- every suite's end_per_testcase now calls a local
verify_cache_invariant/1 helper that asserts verify_caches/0 returns
ok before teardown. A failed verify is a fatal CT failure with
ct:pal'd Mismatches. All 118 pre-existing testcases still pass with
the wiring in place: the H0b write paths populate caches correctly.
New direct CT coverage (graphdb_mgr_SUITE cache_audit group, +4):
- verify_caches_clean_after_bootstrap
- verify_caches_detects_poisoned_parents
(poison node 6's parents -> [9999], expect mismatch tuple)
- verify_caches_detects_poisoned_classes
(poison node 7's classes -> [42], expect mismatch tuple)
- rebuild_caches_restores_after_poison
(3 caches poisoned, rebuild, then verify ok)
Tests: 122 CT (+4) + 64 EUnit = 186 all green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Arcs become the sole authoritative source of hierarchy in the
bootstrap file. Node tuples no longer carry a Parent integer; the
existing inline %% arc comments make the file human-followable
top-to-bottom.
apps/graphdb/priv/bootstrap.terms:
Old: {node, Nref, Kind, Parent, {NameAttr, Name}, AVPs}
New: {node, Nref, Kind, {NameAttr, Name}, AVPs}
All 31 node tuples converted; 30 relationship terms unchanged;
documentation comments updated to describe the new shape.
graphdb_bootstrap.erl:
- classify_terms/4, sort_nodes_by_kind/1, validate/2 match the
5-tuple form.
- term_to_node/1 sets parents=[], classes=[] (caches are filled
by rebuild_caches/0 once all arcs are written).
- do_load/0 calls rebuild_and_verify_caches/0 after writing all
nodes and arcs. rebuild_and_verify_caches/0 invokes
graphdb_mgr:rebuild_caches/0 then graphdb_mgr:verify_caches/0;
a verify mismatch throws {bootstrap_cache_invariant_failed,
Mismatches} as a fatal startup error.
graphdb_bootstrap_tests:
- All 6-tuple node terms in classify_terms/sort/validate/
term_to_node tests rewritten to 5-tuples.
- List-comprehension matchers updated from {node,_,K,_,_,_} to
{node,_,K,_,_}.
- term_to_node tests now expect parents=[] (the rebuild step
happens at load/0 level, outside term_to_node's contract).
graphdb_bootstrap_SUITE:
- Two error-path tests (load_missing_nref_start,
load_nref_above_floor) updated to write the new tuple shape.
Tests: 122 CT + 64 EUnit = 186 all green. graphdb_bootstrap_SUITE
load tests still see correct parents/classes caches because the
new rebuild step populates them; the existing
load_root_node_correct, load_attribute_node_correct,
load_template_avp_node_correct, and load_category_children
testcases all continue to pass with the same expected values.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the H0 series. The "arcs authoritative; hierarchy lists
cached" invariant is now a first-class architectural property of
the kernel, summarised in ARCHITECTURE.md and enforced by
graphdb_mgr:verify_caches/0 at every CT testcase boundary and the
bootstrap loader's tail.
ARCHITECTURE.md updates:
- Status table: 186 tests (was 156); graphdb_mgr line names cache
audit/repair as a feature.
- §2 Storage indexes: drop the retired nodes parent index; document
the arc-based downward-query path.
- §3 Node Record: new record schema (parents/classes lists);
new "Cache invariant" subsection covering the three rules,
cache-field source table, single-writer ownership, and the
verify_caches/0 / rebuild_caches/0 audit APIs. Adds template
to the node-kinds table.
- §8 Bootstrap: 5-tuple node format documented; loader description
notes the rebuild + verify pass and the
bootstrap_cache_invariant_failed startup error.
- §9 Inheritance: Priority 3 walks node.parents (single-chain today,
DAG via H3); H1+H2 dropped from "known correctness gaps" since
they landed in a078373.
- §10 Open Questions: "Composition: dual storage" removed (resolved
by H0); multi-inheritance entry rephrased to reflect that the
cache-list infrastructure already landed.
arcs-authoritative.md: status flipped to Accepted with the H0a-H0e
commit chain; "Future work" notes the doc fold.
TASKS-HIGH.md: H0 marked RESOLVED with a commit-by-commit summary.
The header preamble now reflects that H0 has landed (was: "in
flight"); H3-H5 framed as additive work atop the cache
infrastructure rather than tangled with schema migration.
TASKS-MEDIUM.md: M1 marked RESOLVED, pointing at H0 + the
arcs-authoritative.md / ARCHITECTURE.md §3 references.
CLAUDE.md (root + apps/graphdb): node record schema updated;
apps/graphdb/CLAUDE.md describes the cache-invariant + audit APIs in
place of the retired parent index.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
david-w-t
added a commit
to david-w-t/SeerStoneGraphDb
that referenced
this pull request
May 9, 2026
All H-tasks (H0a-H0e, H1+H2, H3, H4+H5) landed in PRs davidwt-com#10 and davidwt-com#12. Per the task-file retirement workflow (close-out PR keeps the file with finish markers; the next PR opens with a removal commit), drop the file and prune cross-references in CLAUDE.md, README.md, ARCHITECTURE.md, arcs-authoritative.md, TASKS-MEDIUM.md, and the two app-level CLAUDE.md files.
david-w-t
added a commit
that referenced
this pull request
May 9, 2026
* M4: atomic reciprocal attribute pair
graphdb_attr:create_relationship_attribute/3 now writes both attribute
nodes and all four compositional arc rows inside a single Mnesia
transaction. Previously the forward and reciprocal nodes were created
in separate transactions; if the second aborted, the database was left
with an orphan forward attribute and no usable reciprocal.
New private helper do_create_relationship_attribute_pair/3 allocates
the 2 node nrefs and 4 arc-id nrefs outside the transaction (avoiding
side-effects on retry) and writes 6 rows in one txn.
Test: create_relationship_attribute_pair_atomic asserts the row deltas
on `nodes` and `relationships` after a successful pair creation are
exactly +2 and +4 respectively, and that both nodes have exactly one
parent->child arc into them under nref 8.
Tests green: 146 CT + 64 EUnit = 210.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* M3: validate add_relationship endpoints and target_kind
graphdb_instance:add_relationship now runs an explicit validation pass
before resolving classes/templates and writing arcs. Five new failure
modes, all returning structured {error, _} tuples:
- {source_not_found, Nref}
- {target_not_found, Nref}
- {characterization_not_found, Nref}
- {reciprocal_not_found, Nref}
- {characterization_not_an_attribute, Nref, ActualKind}
- {reciprocal_not_an_attribute, Nref, ActualKind}
- {target_kind_mismatch, ExpectedKind, ActualKind}
The four endpoint reads run in one mnesia:transaction. target_kind is
sourced from the seeded `target_kind` literal-attribute (graphdb_attr);
the nref is fetched once at graphdb_instance init via
graphdb_attr:seeded_nrefs() and cached in a new gen_server state
record. Arc-label nodes that lack a target_kind AVP (relationship-
type bucket nodes, legacy data) skip the kind check.
The state record is a clean break from the previous `[]` carrier; only
the new validation needs it, but threading it through add_relationship
keeps the gen_server signature consistent.
Tests: +5 CT cases under the `relationships` group covering missing
source/target, non-attribute characterization, non-attribute
reciprocal, and target_kind mismatch (target_kind=class vs. instance
target). 151 CT + 64 EUnit = 215 green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* M5: per-arc AVPs at connection creation
graphdb_instance:add_relationship/6 (S, C, T, R, TemplateNref,
{FwdAVPs, RevAVPs}) -> ok | {error, _} accepts per-direction user AVPs
and stamps them on the connection rows alongside the auto-applied
Template AVP. Per-direction (asymmetric) is required by §5: connection
metadata such as provenance, confidence, weights, and validity windows
is direction-specific.
The Template AVP stays at index 0 of each row's avps list; user AVPs
follow. /4 and /5 stay non-breaking and pass {[],[]} to /6 internally.
write_connection_arcs is updated to take {FwdAVPs, RevAVPs} and the
gen_server message form gains AVPSpec as a 7th tuple element.
Tests: +3 CT cases under the `relationships` group:
- add_relationship_stamps_user_avps -- user AVP is present alongside
Template AVP on the forward row
- add_relationship_avps_are_per_direction -- a forward-only AVP must
not leak into the reverse row, and vice versa
- add_relationship_default_avps_empty -- /4 still produces a row
with exactly the Template AVP
154 CT + 64 EUnit = 218 green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Docs: M3 + M4 + M5 closeout in TASKS-MEDIUM, refresh test counts
Mark M3, M4, M5 RESOLVED in TASKS-MEDIUM.md with status blocks
describing the API and test additions for each. Bump README and
ARCHITECTURE test counts (209 -> 218; 145 CT -> 154 CT). Update
graphdb_attr_SUITE row to mention atomic reciprocal pair, and
graphdb_instance_SUITE row to mention M3 validation and M5 per-arc
AVPs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Retire TASKS-HIGH.md
All H-tasks (H0a-H0e, H1+H2, H3, H4+H5) landed in PRs #10 and #12.
Per the task-file retirement workflow (close-out PR keeps the file
with finish markers; the next PR opens with a removal commit), drop
the file and prune cross-references in CLAUDE.md, README.md,
ARCHITECTURE.md, arcs-authoritative.md, TASKS-MEDIUM.md, and the
two app-level CLAUDE.md files.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lands the H0 series — establishes the "arcs authoritative; hierarchy lists cached" invariant — plus two H-severity inheritance fixes (H1, H2) and the close-out of TASKS-CRITICAL.md.
The kernel now treats
relationshipsas the sole authoritative source for taxonomy / composition / instantiation.node.parentsandnode.classesare caches; their consistency is asserted bygraphdb_mgr:verify_caches/0after every CT testcase and at bootstrap completion.Commits (7)
33bc848a078373resolve_from_classwalks class taxonomy; P4 filters tokind=connectiond5a7244arcs-authoritative.mdand the H0 substep checklist0b5fc43node.parent; addparents/classescache lists; arc-based downward lookupsce07cb2graphdb_mgr:verify_caches/0+rebuild_caches/0; CT enforcement9e5d64abootstrap.termsto Option B (5-tuple node form); loader rebuilds + verifiesf2fead8Schema change
The `{index, [parent]}` secondary index on `nodes` is retired; downward queries ("children of X") read outgoing arcs from `relationships` filtered by kind + characterization.
Closes
Test plan
🤖 Generated with Claude Code