Skip to content

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

  1. Caller to EntityResolver: resolve — The caller passes a local identifier plus source and target namespaces, asking for a canonical FederatedEntity.
  2. 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.
  3. 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.
  4. EntityResolver to FederationRegistry: resolve_across_projects — The canonical URI is resolved to a ProjectBinding that identifies which registered ecosystem owns the entity.
  5. EntityResolver to ModelFacade: get_full — The facade for the target project is asked for the full DomainEntity record so the federated response can carry data, not just an identifier.
  6. EntityResolver to ChangeTracker: record — A ModelChange row is appended to the audit log, capturing the resolution event for governance.
  7. EntityResolver to EventBus: publish(federation.resolved) — A federation.resolved event is emitted with the canonical URI and similarity score in the payload.
  8. EntityResolver to Caller: FederatedEntity — A FederatedEntity wrapping the canonical URI, similarity score, and enriched entity is returned.
  9. Conflict branch: publish(federation.conflict) — If no mapping exists or similarity falls below threshold, a federation.conflict event fires instead and the caller receives None.

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.pyEventType.FEDERATION_RESOLVED, EventType.FEDERATION_CONFLICT