Skip to content

Auto-Commit Inverse SmartLinks During Relationship Persistence (Commit Pass 2) #442

@owleyeview

Description

@owleyeview

1. Summary

Extend Pass 2 of commit_functions::commit() so that when a declared relationship is persisted as a SmartLink, the commit logic also resolves and persists the corresponding inverse SmartLink automatically.

This enforces the MAP schema contract that all declared relationships are bidirectional and materialized, and ensures graph traversal works correctly from both directions.


2. Problem Statement

The MAP type system defines DeclaredRelationshipType and InverseRelationshipType as a paired schema contract:

  • A declared relationship links to its inverse via HasInverse
  • The inverse links back via InverseOf
  • This guarantees bidirectional traversal of relationships

However, Pass 2 of commit_functions::commit() currently:

  • Persists only forward SmartLinks from the declaring side
  • Does not resolve or persist inverse relationships
  • Does not enforce the schema requirement that inverses must exist

As a result:

  • Graph traversal from the target side may fail silently
  • Schema guarantees are not enforced at persistence time
  • Relationship consistency depends on caller behavior (which is incorrect)

Importantly:

Inverse relationships are not directly writable via the MAP API

Therefore:

  • Callers cannot and should not stage inverse relationships
  • Commit-time population is the only valid mechanism for materializing inverse links
  • Missing inverses represent a schema contract violation

3. Scope

This issue focuses exclusively on:

  • Resolving inverse relationship names from the type system
  • Persisting inverse SmartLinks during Pass 2
  • Enforcing declaring-side-only mutation semantics

This issue does not include:

  • Changes to the StagedHolon state machine
  • Relationship source anchoring refinements (see Issue 2)
  • Duplicate detection or idempotency behavior (see Issue 3)

4. Proposed Solution

4.1 Commit-Side Inverse Resolution

For each declared relationship collection being committed:

  1. Determine the source LocalId used for forward SmartLink persistence
  2. Follow DescribedBy to the source holon’s TypeDescriptor
  3. Follow InstanceRelationships to candidate DeclaredRelationshipType descriptors
  4. Match the relationship name against the descriptor’s type_name
  5. Follow HasInverse to the corresponding InverseRelationshipType
  6. Read the inverse descriptor’s type_name to determine the inverse relationship name
  7. For each forward relationship member:
    • Persist the forward SmartLink
    • Persist the inverse SmartLink with endpoints reversed

4.2 Declaring-Side-Only Mutation Rule

  • Relationship mutations originate only from the declaring (forward) side
  • Inverse relationships are never staged or directly written by callers
  • Commit is responsible for inverse materialization

4.3 Source Selection (Current Behavior)

This issue reuses the existing logic for determining the source LocalId of forward SmartLinks.

⚠️ Note:

  • This behavior will be refined in Issue 2, which introduces explicit staged states (ForUpdateNewVersion, ForUpdateGraphOnly) and a formal relationship anchoring rule
  • When Issue 2 lands, both forward and inverse persistence must derive their source from that rule rather than assuming the staged holon is always the source

4.4 Bootstrap Safety

Inverse resolution must work when schema holons are:

  • staged (during bootstrap), or
  • already persisted

Traversal should:

  • read staged relationship maps when available
  • fall back to persisted graph traversal otherwise

The invariant is:

Schema closure must be resolvable from staged or saved state


4.5 Schema Invariant

All DeclaredRelationshipType descriptors are expected to have an inverse.

If:

  • a declared relationship is matched, and
  • no HasInverse target can be resolved

then:

  • this must be treated as a schema error, not silently ignored

4.6 External Target Constraint

Inverse persistence is only valid when the target holon is local.

If a relationship target resolves to a non-local holon:

  • forward SmartLink may still be written (if supported by current logic)
  • inverse SmartLink must not be written locally

This defines a boundary of schema contract enforcement.


5. Scope and Impact

Component File(s) Impact
Commit Pass 2 commit_functions.rs Primary implementation site
SmartLink persistence smartlink_adapter.rs Likely unchanged
Type graph traversal descriptor resolution logic Extended for inverse lookup

Out of scope:

  • StagedHolon state model changes (Issue 2)
  • Duplicate detection and idempotency (Issue 3)
  • Inverse deletion semantics
  • Cardinality enforcement
  • External/proxied inverse propagation

6. Testing Considerations

  • Verify forward + inverse traversal for committed relationships
  • Verify inverse resolution works with staged schema (bootstrap)
  • Verify inverse resolution works with persisted schema
  • Verify missing HasInverse produces a schema error
  • Verify external targets do not cause commit failure

7. Definition of Done

  • Declared relationships automatically produce inverse SmartLinks at commit
  • Inverse relationships are never staged or directly written by callers
  • Inverse resolution works against both staged and persisted schema
  • Missing HasInverse is treated as a schema error
  • External targets do not trigger invalid inverse writes
  • Existing tests pass without regression
  • At least one end-to-end test validates bidirectional traversal after commit

8. Key Design Principles

  • Inverse relationships are schema-enforced, not caller-supplied
  • All mutations originate from the declaring side
  • Commit enforces schema contracts, not just mechanical persistence
  • Inverse persistence must mirror forward persistence using the same resolved source

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions