Query traversal: Home routing for arc-discovered nrefs - #55
Merged
Conversation
Specs the fix for the SP2 defect filed in PR davidwt-com#53: graphdb_query's BFS re-guesses which store each frontier node lives in, so a project with >=6 instances shadows bootstrap nref 6 and an environment-only path returns {ok, no_path}. Two halves. (A) arc-discovered nrefs route through a new pure graphdb_ns:arc_target_namespace(Home, Kind, Char) keyed on #relationship.kind, with the 29/30 membership pair distinguished by characterization. (B) the BFS frontier, visited set, and target comparison become Home-qualified -- without B, project-6 and environment-6 collapse into one visited entry and the bare-nref target test reports a false found. Corrects the fix direction recorded in the PR davidwt-com#53 comment and in TASKS.md, which said to route via graphdb_ns:target_namespace/2 on the arc's target_kind. Bootstrap arc labels 21-30 carry no target_kind AVP, and arcs 23/24 are the decisive hops in the repro, so that lookup fails exactly where routing matters most. Design only -- no code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Five tasks, TDD throughout. T1 graphdb_ns:arc_target_namespace/3 + EUnit;
T2 Home-qualified BFS (the headline invariant + the false-found case);
T3 home key on cross-store edges; T4 resume/2 continuation-Home gate;
T5 docs + verification gate.
Records one deliberate deviation from the design: its T3 (visiting
project-6 must not suppress environment-6) is not constructible under
this scope, since reaching both keys in one walk needs a store crossing
and the only one available lands on a class, from which the environment
attribute subtree is unreachable by taxonomy. Replaced by T3', which
asserts the same Half-B property observably via #cont_path{} state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Adds validate_cont_homes/2, a caller-side gate on resume/2 that runs
after validate_session_home/1 and before the gen_server:call. Every
home_id() a #cont_path{} carries (target + frontier) must resolve
against the resuming session's own bound project; a mismatch returns
{error, session_project_mismatch} instead of reaching home_of_id/2
inside the graphdb_query singleton.
Closes two review findings tracked from Task 2:
- a continuation resumed under a project-less session no longer risks
maps:get(project, Session) raising {badkey, project} inside
handle_call/3 and killing the singleton
- a continuation resumed under a *different* project is rejected
instead of home_of_id/2 silently discarding the anchor and reading
the wrong project's tables
Adds three regression tests: t3_continuation_state_is_home_qualified
and t6_resume_round_trip_under_project_session (pass on arrival --
Task 2 already delivered this shape) and
resume_rejects_foreign_project_continuation (drives this fix).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
…frontier gate
Six findings from the whole-branch final review:
1. docs/Architecture.md: correct the false claim that bootstrap arc labels
21-30 carry target_kind (they don't -- only runtime-created relationship-
attribute pairs do); note arc-discovered traversal routes via
graphdb_ns:arc_target_namespace/3 instead, and that target_namespace/2
has no production caller.
2. graphdb_query.erl: rewrite resume/2's stale header comment -- it no
longer dispatches through resolve_home/2; validate_session_home/1 is
still load-bearing because validate_cont_homes/2 depends on it running
first.
3. apps/graphdb/CLAUDE.md: document resume/2's {error, invalid_project}
and {error, session_project_mismatch} returns alongside the existing
{error, snapshot_expired}.
4. graphdb_query.erl: validate_cont_homes/2 folds a frontier-element shape
check into its lists:all/2 pass, closing a gap where a wrong-arity
frontier tuple was silently dropped by the old list-comprehension
generator instead of failing the gate. Adds
resume_rejects_malformed_frontier_continuation/1 to
graphdb_query_SUITE's sp2_traversal_home_routing group, asserting both
the clean error and that the graphdb_query singleton pid survives.
5. graphdb_ns.erl: Rev PA3 revision-history entry for arc_target_namespace/3.
6. graphdb_query.erl: remove the dead `[] -> []` arm in bfs/8's
depth-exhausted clause -- unreachable because the first bfs/8 clause
already matches an empty frontier for any depth.
Verification: rebar3 compile / as test compile zero warnings, rebar3 xref
clean, make test-ct-parallel 551/551, rebar3 eunit 151/151.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the defect filed against PR #53 (comment) and recorded in
TASKS.md:#q_find_path{}silently returned{ok, no_path}for a path that exists.The defect
resolve_home/2resolves a bare nref by trying the session's bound project first and falling back to the environment. That guess is correct and deliberate for entry points, where the query language has no characterization context to route on. Butsession_read_arcs/4andis_scaffold_node/2applied it to arc-discovered nrefs too, re-deciding the store for every node BFS reached.Project allocators start at 1, so any project with ≥6 instances shadows bootstrap nref 6 and ≥35 shadows the whole scaffold. Two runtime environment attributes whose only route runs through nref 6:
Expanding
6, the walk resolved it to the project's instance 6, readrelationships_<Anchor>, found no taxonomy arcs, and the frontier emptied.A second failure, not in the original report
Tracing turned up a worse case.
Visitedwas keyed by bare nref and the target test compared bare integers, so withto = 6under a shadowing project a walk reaching the environment's attribute 6 reported{found, Path}— a fabricated path claiming to have reached the project's instance 6. A wrong answer rather than a missing one.The fix
Half A — arc-discovered nrefs stop being ambiguous. New pure
graphdb_ns:arc_target_namespace(Home, Kind, Char)derives the target's store from the arc traversed, keyed on#relationship.kind. The instance↔class membership pair is the one arc shape whose ends deliberately live in different Homes, and its characterization is exactly what says which direction is being walked:The catch-all is deliberate: this is reachable from the
graphdb_querysingleton, where afunction_clausewould take the query server down for every session over one malformed row.Half B — traversal state is Home-qualified.
home_id() :: environment | {project, Anchor}now keys the visited set, the frontier,#cont_path{}, and the public result.resume/2gains a caller-sidevalidate_cont_homes/2returning{error, session_project_mismatch}.A path edge gains
homeiff the hop crosses stores — absent means "same store as the previous hop". The value is ahome_id(), never the raw project handle, so physical table atoms stay out of query results.Correction to the fix direction I filed on #53
That comment said to route via
graphdb_ns:target_namespace/2on the arc label'starget_kindAVP. That does not work. Bootstrap arc labels 21–30 carry notarget_kind—bootstrap.terms:122-131creates all ten with[], andgraphdb_attr:init/1retro-stamps onlyattribute_type. Arcs 23/24 are the decisive hops in the repro, so the lookup fails precisely where routing matters.docs/Architecture.mdasserted the same false premise and is corrected here.Not changed, deliberately
resolve_home/2,session_read_arcs/4, andsession_read_node/2are untouched. Entry points still use the intent-following guess, andresolve_home_prefers_project_and_logs_on_collisionplus the wholeq6_find_pathgroup pass unmodified — the executable proof that entry-point behaviour is intact. Onlybfs_step/5changed caller, to the pre-existingsession_read_arcs_home/5.Still open, filed in TASKS.md
#q_instances_of{}keeps its special case.target_kindonto arc labels 21–30, which would letgraphdb_instance:check_target_kind/3drop its permissive legacy arm.Verification
551 CT + 151 EUnit,
rebar3 xrefclean with no new suppressions,compileandas test compilezero warnings.Reverting Half A reproduces the filed
no_pathand surfaces the cache entry{arcs, #{anchor => …}, 6, outgoing, [taxonomy]} => []— the environment's nref 6 read from the project's table. Reverting Half B's target qualification reproduces the fabricated one-edge path. Neither new test passes vacuously.Design:
docs/designs/query-traversal-home-routing-design.md🤖 Generated with Claude Code
https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF