Skip to content

SP1: project/environment reference & namespace model#52

Merged
david-w-t merged 11 commits into
davidwt-com:mainfrom
david-w-t:develop
Jul 1, 2026
Merged

SP1: project/environment reference & namespace model#52
david-w-t merged 11 commits into
davidwt-com:mainfrom
david-w-t:develop

Conversation

@david-w-t

Copy link
Copy Markdown
Contributor

Sub-project 1 of the project/environment separation program
(design: docs/designs/project-env-reference-namespace-model-design.md).
Establishes, at the API/code layer only — no node/relationship record
changes
, that every nref reference has a derived namespace and that project
operations require a session. Behaviour-preserving against today's single
Mnesia store
; SP2 gives the seam physical teeth.

What landed

Milestone A — mechanisms (subagent-implemented + independently reviewed)

  • graphdb_ns — pure namespace map (namespace_of/1, target_namespace/1environment | project | home); exhaustive table-driven tests. Intentionally unused by production code in SP1 (the classifier the SP2 store-router will consume).
  • graphdb_project — project registry (register_project/1, is_project/1) creating the nref-5 anchor; project session (open_session/1, session_project/1, require_session/1).
  • Proxy representation contract — seeded "Remote Reference" class + remote_project/remote_nref literal attributes + graphdb_instance:is_proxy/1 / proxy_coordinates/1. Cross-project links are local proxy nodes carrying remote coordinates as AVP payload; no structural reference crosses a project boundary. Representation only (creation/dereference are SP2/SP3). Ontology-tree diagram updated.

Milestone B — required-session threading & relocation

  • Project write ops take a Session first arg and reject a missing/invalid one with {error, invalid_session}: create_instance/4,5,6, add_relationship/5,6,7, remove_relationship/4,5, update_relationship/5,6 (+_both), add_class_membership/3; graphdb_mgr:create_instance/4 + add_relationship/5 gain the session too.
  • Relationship mutation relocated to the canonical project surface in graphdb_project (env/project split). Tier-1 *_in_txn primitives untouched → graphdb_rules firing unaffected.
  • Scope refinement: mutate/1 and the instance reads (get_instance/children/compositional_ancestors/resolve_value) stay namespace-agnostic in SP1 (like get_node/get_relationships): mutate/1 is a mixed env/project batch, and the reads are consumed by graphdb_query (gating them would force the deferred query-session unification). Their routing lands in SP2.

Testing

Full suite green: 510 CT + 141 EUnit = 651, 0 failures, 0 warnings. New bad-session gate tests cover every gated project-write family; also cleared two latent warnings on the branch.

Deferred (tracked in TASKS.md → Multi-project sessions)

SP2 physical per-project store + allocator-from-1; SP3 distribution/residency + proxy dereference; SP4 migration. Also: proxy_coordinates/1 missing-AVP safety (harmless until SP2 proxy creation); session unification with graphdb_query; private environment overlays.

🤖 Generated with Claude Code

david-w-t and others added 10 commits June 29, 2026 23:01
Sub-project 1 of the project/environment separation program. Establishes,
at the API/code layer only (no record changes), that every nref reference
has a derived namespace ({environment | current-project}), cross-project
links indirect through local proxy nodes (AVP payload, no new kind), a
project-session opened against a nref-5 registry node, and the env-op vs
project-op API split that relocates relationship mutation off "instance".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
8 tasks in two PR-sized milestones — A: mechanisms (graphdb_ns pure
resolution, project registry, open_session, Remote Reference proxy
contract); B: required-session threading through the project op surface +
relationship-mutation relocation per the env/project split. Behavior-
preserving against today's single store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Adds graphdb_project (plain module, not gen_server) with:
- register_project/1: creates a kind=instance node under Projects (nref 5)
  via category composition arcs (ARC_CAT_CHILD/ARC_CAT_PARENT), atomically
  through graphdb_mgr:transaction/1; allocates nref + rel-id pair outside
  the transaction fun (load-bearing deadlock invariant).
- is_project/1: returns true iff the node's parents cache includes
  NREF_PROJECTS (5).

Adds graphdb_project_SUITE with 2 CT cases (503 total, +2). TDD: RED
(undef) then GREEN (both pass). Cache-invariant teardown verified: ARC_CAT_PARENT
(21) is in PARENT_ARCS, kind=composition — verify_caches reconciles correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Seed a "Remote Reference" class (under Classes, nref 3) and two literal
attributes (`remote_project`, `remote_nref`) via idempotent init/1
seeding in graphdb_instance and graphdb_attr respectively.  Add proxy
recognizers: `is_proxy/1` (class-membership check) and
`proxy_coordinates/1` (AVP extraction).  Ship two CT cases verifying
the recognizer accepts a well-formed proxy node and rejects a plain
instance.  Update ontology-tree.md and bump the attr-count assertion in
graphdb_attr_SUITE to account for the two new literal seeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Thread a required project Session (first arg) through the relationship-
mutation API: graphdb_instance add_relationship/5,6,7, add_class_membership/3,
remove_relationship/4,5, update_relationship/5,6, update_relationship_both/5,6
each gate on graphdb_project:require_session/1 (via with_session/2) before any
store access; a missing/invalid session returns {error, invalid_session}.
Tier-1 *_in_txn primitives are untouched (graphdb_rules composes those and is
unaffected). graphdb_project re-exports the surface as the canonical project
API home (env/project split); graphdb_mgr:add_relationship gains the session.

Behaviour-preserving against the single store (session validated but inert).
Suites thread a memoised real session (sess() helper, pre-warmed in
init_per_testcase so its arcs are in the baseline before delta captures).
New gate tests cover both mechanisms (tier-2 plain + gen_server wrapper).
Also removed two latent warnings on this branch (dead groups/0 and unused
#relationship record in graphdb_project_SUITE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Thread a required project Session (first arg) through create_instance/4,5,6
(graphdb_instance) and graphdb_mgr:create_instance/4, gating on
graphdb_project:require_session/1 before any store access. The rule-firing
engine mints children inside create_instance's own transaction via tier-1
primitives (graphdb_rules never calls create_instance), so no further
propagation is needed. Behaviour-preserving against the single store.

All create_instance test call sites (186) thread the memoised sess() helper;
new create_instance_rejects_bad_session gate test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Document SP1 across Architecture.md §6, apps/graphdb/CLAUDE.md, root CLAUDE.md,
and TASKS.md (Multi-project sessions): the graphdb_ns namespace map, the
graphdb_project registry + required project session, the env/project write-op
split, the Remote Reference proxy contract, and the four-sub-project program
(SP1 done; SP2 physical store, SP3 distribution, SP4 migration).

Records the Task 7 scope refinement: mutate/1 and the instance reads
(get_instance/children/compositional_ancestors/resolve_value) stay
namespace-agnostic in SP1 (mutate is mixed env/project; reads are
query-engine-coupled), deferred to SP2 — only the unambiguous project write
ops are session-gated. Also notes the SP2 follow-up for proxy_coordinates/1
missing-AVP safety.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Add bad-session gate tests for the update-* and add_class_membership families
(every gated project-write family now has a bad-session assertion). Document
two deliberate SP1 choices in the design spec: graphdb_ns is intentionally
unused by production code in SP1 (it is the pure classifier the SP2 store-router
will consume, not dead code), and require_session/1 is shape-only (registry
existence is validated once by open_session/1; per-op re-checking is deferred
to SP2 when the session binds to physical storage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Comment thread apps/graphdb/src/graphdb_project.erl
@david-w-t

Copy link
Copy Markdown
Contributor Author

Is there anything preventing (or making it difficult) to have more than one project open in a session?

The session is bound to one project in SP1 but nothing forecloses several:
the session value is fully encapsulated (only graphdb_project constructs/reads
it; write ops gate via require_session/1 and assume no single-project-ness), so
widening the shape is localized. The remaining work is semantic (write
targeting; cross-project rule/overlay priority) and lands with SP2; resolving
existing data is unaffected because no structural reference crosses a project
boundary (proxy indirection).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
@david-w-t

Copy link
Copy Markdown
Contributor Author

Nothing prevents it, and little makes it hard — the hard part is semantics, and that lands with SP2, not here.

Why it's not blocked. The session is a deliberately opaque value: only graphdb_project constructs it (open_session/1) and inspects it (session_project/1, require_session/1). graphdb_mgr and graphdb_instance touch it only through require_session/1 and never pattern-match the shape, and in SP1 nothing routes on project yet (the session is inert). So no code assumes single-project-ness — widening #{project => Nref} to a working set + a current/default is a localized change to ~3 functions in one module.

Where the real work is (SP2+):

  1. Write targeting — with projects [A, B] open, which does create_instance(Session, …) land in? A current/default project in the session + per-op override, or an explicit per-op project arg. An API decision, not a mechanical blocker.
  2. Rule/overlay priority — a declared precedence across open projects (env → A → B), already anticipated in TASKS.mdMulti-project sessions (list of {ProjectId, AnchorNref} + priority).
  3. What doesn't get harder — resolving existing data. Because no structural reference crosses a project boundary (cross-project links are proxy nodes), a row's project-local nrefs belong to its home project, so reads/traversal stay well-defined per-row-home even with several projects open. The only ambiguity is for new writes (targeting) and priority.

"Multiple open" is a working-set/targeting convenience for a principal with access to each; it doesn't dissolve the isolation boundary.

Recommendation: keep it singleton in SP1 (behavior-preserving/YAGNI) — it's forward-compatible, and multi-project naturally lands with SP2 when writes actually route. I've added a "multi-project-session forward compatibility" note to the design doc's deferred section (commit 2cc6b75).

@david-w-t david-w-t merged commit 76ef6bb into davidwt-com:main Jul 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant