Skip to content

[Feature]: Introduce a transport-agnostic model session owning identity scope and library index lifecycle #345

Description

@antoineatstariongroup

What type of issue is this?

  • Bug report
  • Feature request

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of the SysML2.NET
  • I have searched open and closed issues to ensure it has not already been reported

Description

There is no layer that composes RESTDalSemanticsSerializer.TextualNotation. SysML2.NET.Dal and SysML2.NET.Semantics each reference only SysML2.NET.Extensions, SysML2.NET.REST references only SysML2.NET.Serializer.Json, and ISession is a stub (Open/Close). Every consumer therefore wires the pipeline itself, and two contracts end up with no owner.

1. Identity scope is per loader, so the same element exists as several POCO instances.

DeSerializer creates its XmiDataCache in its constructor and ModelLibraryLoader news up a private DeSerializer, so a library load and a model load produce disjoint object graphs. Elements are deduplicated within a load (keyed on IData.Id, which is parsed from xmi:id and therefore stable) but not across loads.

POCOs use reference equality, and NameResolutionCache relies on ReferenceEquals in 15+ places, so a foreign instance can never match a resolution target — it answers Shadowed and stops the outward walk. This is currently patched inside NameResolutionCache.TranslateToResolutionGraph, which maps implied generals back into the writer's graph by Id. That fix is private to the writer, is untyped (nothing marks an instance as foreign), and every future consumer of ILibraryTypeIndex or the implied layer will hit the same trap.

2. Nothing owns "the library index is complete".

ILibraryTypeIndex must be populated eagerly, before any resolution runs, and must not fault libraries in on a miss — doing so re-enters the bootstrap cycle the implied layer exists to break. No component enforces this. It becomes acute for REST clients, where libraries live in a separate project/commit and RequestElements can be paged: rebuilding the index per response yields an index that looks valid but is incomplete, producing silently missing implied specializations instead of a loud UnresolvedLibraryTypeException. Completeness is a state transition, not a per-response side effect.

Proposed approach

Add a transport-agnostic composition layer (working name SysML2.NET.Workspace, referencing Dal + Semantics) fed by either REST or Serializer.Xmi:

  • Owns a single IAssembler, making its ConcurrentDictionary<Guid, Lazy<IElement>> the one identity scope. One instance per Id removes the duplication by construction for both transports — the XMI path currently has no shared store at all.
  • Exposes an explicit libraries complete transition. Implied computation and textual-notation writing are unavailable before it; after it the index is built once from the assembled graph, with explicit invalidation when a later Synchronize touches library elements.
  • Builds ILibraryTypeIndex via the existing OwnershipTreeLibraryTypeIndex.Build(roots) — whose parameter doc already says "typically the referenced Namespaces reported by a deserializer", i.e. the consumer's own graph. Library roots can be selected from the graph itself via Element::isLibraryElement (implemented as LibraryNamespace() != null), avoiding threading project/commit provenance through the DAL.

Deliberately not doing:

  • Populating the index from Assembler. That would make Dal depend on Semantics, and hands an incremental component (Synchronize is additive and handles deletes) an invariant it cannot check, since it does not know when resolution runs.
  • Extending SysML2.NET.REST.Session to do this, which would couple transport to semantics and leave the file path unable to reuse it. Session should feed the new layer instead.
  • Changing ILibraryTypeIndex. TryGetType(qualifiedName, out IType) is the right seam, and AddSysML2Semantics already declines to register one — the new layer simply becomes that caller.

Follow-ups once the invariant holds: NameResolutionCache.TranslateToResolutionGraph / BuildResolutionGraphIndex / IsInResolutionGraph become dead and should be deleted rather than kept as a silent safety net, and DeSerializer may want a constructor accepting an externally supplied IXmiDataCache.

Steps to Reproduce

System Configuration

  • SysML2.NET version: 0.22.0
  • Environment (Operating system, version and so on): Windows 11
  • .NET Framework version: .NET 10 / netstandard2.1
  • Additional information: surfaced while validating textual notation for 08-Requirements ([Feature]: Textual Notation validation — 08-Requirements (Requirements) #295); the writer-side workaround is described in NameResolutionCache.TranslateToResolutionGraph

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions