Federation Entity Resolution¶
This diagram traces how an entity known in one ecosystem (the source namespace) is resolved to its canonical form in a different ecosystem (the target namespace). The entry point is EntityResolver.resolve(local_id, source_namespace, target_namespace) at src/fcc/federation/entities.py:78. A developer reads this trace to understand how vocabulary mappings, namespace URIs, and change-tracking interact when a cross-project lookup succeeds — or, more importantly, when it fails with a conflict. The flow is load-bearing for all 11 registered ecosystems and is the runtime path behind VocabularyProviderPlugin consumers.
The sequence below shows a successful cross-namespace lookup with enrichment and audit-trail write, plus the conflict branch.
sequenceDiagram
participant Caller
participant EntityResolver
participant VocabularyMapping
participant NamespaceRegistry
participant FederationRegistry
participant ModelFacade
participant ChangeTracker
participant EventBus
Caller->>EntityResolver: resolve(local_id, source_ns, target_ns)
EntityResolver->>VocabularyMapping: lookup(source_ns, local_id)
VocabularyMapping-->>EntityResolver: {target_id, similarity_score}
alt mapping found
EntityResolver->>NamespaceRegistry: resolve_uri(target_ns, target_id)
NamespaceRegistry-->>EntityResolver: canonical_uri
EntityResolver->>FederationRegistry: resolve_across_projects(canonical_uri)
FederationRegistry-->>EntityResolver: ProjectBinding
EntityResolver->>ModelFacade: get_full(target_id)
ModelFacade-->>EntityResolver: DomainEntity
EntityResolver->>ChangeTracker: record(ModelChange)
Note over EventBus: emits federation.resolved
EntityResolver->>EventBus: publish(federation.resolved)
EntityResolver-->>Caller: FederatedEntity
else no mapping or low similarity
Note over EventBus: emits federation.conflict
EntityResolver->>EventBus: publish(federation.conflict)
EntityResolver-->>Caller: None
end
Failure modes concentrate in the mapping and namespace steps. A missing VocabularyMapping returns None from the lookup — the resolver does not raise — so the federation.conflict event is the only signal. An unregistered target namespace raises KeyError from NamespaceRegistry.resolve_uri; this is a hard configuration error rather than a data gap. ModelFacade.get_full can return None if the canonical ID is valid in the registry but the underlying object model has not been populated, in which case the FederatedEntity still returns with entity=None and the caller must handle partial enrichment. To observe the flow, subscribe to both federation.resolved and federation.conflict; the ratio of the two is a useful data-quality metric for each namespace pair.
The ChangeTracker.record call writes a ModelChange audit row even for successful reads — this is intentional, because cross-project resolutions are treated as governance events. If auditing is disabled via configuration, the tracker is a no-op. Instrumentation typically wraps EntityResolver.resolve with a @traced span so that the mapping lookup and the facade enrichment show up as separate child spans, which is useful for diagnosing slow namespaces.
Steps in detail¶
- Caller to EntityResolver: resolve — The caller passes a local identifier plus source and target namespaces, asking for a canonical
FederatedEntity. - EntityResolver to VocabularyMapping: lookup — The resolver queries the mapping store for a row keyed by
(source_ns, local_id); it returns{target_id, similarity_score}or nothing. - EntityResolver to NamespaceRegistry: resolve_uri — If a mapping was found, the target ID is expanded against the target namespace's URI template to produce a canonical URI.
- EntityResolver to FederationRegistry: resolve_across_projects — The canonical URI is resolved to a
ProjectBindingthat identifies which registered ecosystem owns the entity. - EntityResolver to ModelFacade: get_full — The facade for the target project is asked for the full
DomainEntityrecord so the federated response can carry data, not just an identifier. - EntityResolver to ChangeTracker: record — A
ModelChangerow is appended to the audit log, capturing the resolution event for governance. - EntityResolver to EventBus: publish(federation.resolved) — A
federation.resolvedevent is emitted with the canonical URI and similarity score in the payload. - EntityResolver to Caller: FederatedEntity — A
FederatedEntitywrapping the canonical URI, similarity score, and enriched entity is returned. - Conflict branch: publish(federation.conflict) — If no mapping exists or similarity falls below threshold, a
federation.conflictevent fires instead and the caller receivesNone.
See also¶
- Entry point:
src/fcc/federation/entities.py:78 - Vocabulary mapping schema:
src/fcc/objectmodel/mapping.py - Related class diagram:
../class-diagrams/federation-model.md - Related event types:
src/fcc/messaging/events.py—EventType.FEDERATION_RESOLVED,EventType.FEDERATION_CONFLICT