Skip to content

IOntologySource & Builder

Ontology graphs may be assembled from two contribution paths: hand-authored DomainOntology.Define() calls and external IOntologySource implementations that emit ontology events at startup. Both paths feed the same OntologyGraphBuilder, which composes their output, applies the DR-6 cross-provenance merge, and freezes a single OntologyGraph. The fluent DSL exposed during Define() is IOntologyBuilder.

Namespace: Strategos.Ontology.Builder (DSL) and Strategos.Ontology (source/delta types). Source: src/Strategos.Ontology/Builder/IOntologyBuilder.cs, src/Strategos.Ontology/Sources/.

Extension contract for ontology contributions from sources beyond hand-authored definitions. Registered via DI through OntologyOptions.AddSource<T>() and drained at startup by OntologyGraphBuilder.

MemberSignature
SourceIdstring SourceId { get; }
LoadAsyncIAsyncEnumerable<OntologyDelta> LoadAsync(CancellationToken ct)
SubscribeAsyncIAsyncEnumerable<OntologyDelta> SubscribeAsync(CancellationToken ct)

SourceId is the stable identifier the source uses to tag provenance and conflict diagnostics — it appears on every OntologyDelta the source emits and threads through any composition exception that originates from this source. Source IDs must be unique within an ontology composition.

LoadAsync replays the source’s full state as a stream of deltas. Called once at OntologyGraphBuilder.Build() time. Implementations stream their entire current view (object types, properties, links) as OntologyDelta values.

SubscribeAsync subscribes to incremental updates. Strategos 2.5.0 only consumes LoadAsync at startup; SubscribeAsync is part of the contract for forward compatibility with live invalidation (v2.6.0+). Static sources may return an immediately-completing async enumerable.

Implementations are registered as transient in DI — each LoadAsync drain instantiates a fresh source instance:

services.AddOntology(options =>
{
options.AddDomain<TradingOntology>();
options.AddSource<MyExternalIngester>(); // implements IOntologySource
});

The event vocabulary IOntologySource implementations emit. OntologyDelta is an abstract record with eight sealed-record variants covering object-type, property, and link granularity:

VariantFieldsPurpose
AddObjectTypeDescriptor: ObjectTypeDescriptorAdds a new object type to the graph.
UpdateObjectTypeDescriptor: ObjectTypeDescriptorReplaces the descriptor at (DomainName, Name).
RemoveObjectTypeDomainName, TypeName: stringRemoves an object type by identity.
AddPropertyDomainName, TypeName: string; Descriptor: PropertyDescriptorAppends a property to an existing type.
RenamePropertyDomainName, TypeName, FromName, ToName: stringRenames in place — preserves identity through the matcher. Never expanded into a remove/add pair.
RemovePropertyDomainName, TypeName, PropertyName: stringRemoves a property by name.
AddLinkDomainName, SourceTypeName: string; Descriptor: LinkDescriptorAppends a link to a source type.
RemoveLinkDomainName, SourceTypeName, LinkName: stringRemoves a link by name.

Common fields on every delta:

FieldTypeNotes
SourceIdstring (required init)Identifier of the emitting IOntologySource.
TimestampDateTimeOffset (required init)Wall-clock instant of the originating change.

Mechanical ingesters are forbidden from constructing Add/Update deltas whose descriptors contain Actions, Events, or Lifecycle — those are hand-authored intent. Validation runs at delta-apply time and surfaces as AONT205 when violated.

Provenance is carried on ObjectTypeDescriptor.Source (and on each PropertyDescriptor/LinkDescriptor) as a DescriptorSource enum:

  • HandAuthored — declared via the DomainOntology builder DSL.
  • Ingested — emitted by an IOntologySource implementation; SourceId is also set.

When an Add/Update delta lands at a (DomainName, Name) slot already occupied by the opposite provenance, the builder folds the two descriptors through MergeTwo.Merge. Same-provenance collisions surface as AONT040 duplicates downstream. Field-level provenance lets the freeze-time analyzers (AONT201AONT208) compare hand-declared and ingested contributions independently.

The fluent runtime DSL passed to DomainOntology.Define(IOntologyBuilder builder). Implementations are internal; consumers interact through the interface.

MemberSignaturePurpose
Object<T>void Object<T>(Action<IObjectTypeBuilder<T>> configure) where T : classRegisters T with its default descriptor name (typeof(T).Name).
Object<T> (named)void Object<T>(string? name, Action<IObjectTypeBuilder<T>> configure) where T : classRegisters T with an explicit descriptor name. Allows the same CLR type under multiple logical names; name must match ^[a-zA-Z_][a-zA-Z0-9_]*$.
Interface<T>void Interface<T>(string name, Action<IInterfaceBuilder<T>> configure) where T : classRegisters a polymorphic interface backed by the C# interface T.
CrossDomainLinkICrossDomainLinkBuilder CrossDomainLink(string name)Declares a relationship between types in different domain assemblies.
ObjectTypeFromDescriptorvoid ObjectTypeFromDescriptor(ObjectTypeDescriptor descriptor)Registers a fully-specified descriptor directly, bypassing the expression-tree DSL. Mechanism by which IOntologySource contributions reach the graph — ingested types may only be known by SymbolKey with no loaded CLR type.
ApplyDeltavoid ApplyDelta(OntologyDelta delta)Applies a delta to the current builder state. Dispatches by variant; AddObjectType routes to ObjectTypeFromDescriptor. Unknown variants throw NotSupportedException.

Most consumers only call Object, Interface, and CrossDomainLinkObjectTypeFromDescriptor and ApplyDelta are the seams the source-drain uses internally, exposed publicly so test wiring can replicate the drain.