Skip to content

FCC Vocabulary Provider Pattern

The VocabularyProviderPlugin pattern was added in v1.2.1 to give downstream projects a clean, non-runtime-coupled way to contribute vocabularies to the FCC object model. This page walks through the pattern, the authoring process, and the conformance tests.

The reference code lives under:

  • src/fcc/objectmodel/provider_registry.py — plugin protocol & registry
  • src/fcc/objectmodel/vocabulary_loader.py — YAML → domain entity loader
  • src/fcc/objectmodel/mapping.pyVocabularyMapping frozen dataclass
  • src/fcc/objectmodel/federation.py — cross-project federation
  • src/fcc/data/objectmodel/*_vocabulary_mappings.yaml — 19+ shipped mapping files

Why a Plugin, Not a Direct Import

Before v1.2.1, a downstream project that wanted to share vocabulary with FCC had two bad choices:

  1. Import FCC at runtime — tight coupling, circular dependency risk, version fragility
  2. Fork FCC — maintenance burden, no path to upstream improvements

The VocabularyProviderPlugin pattern solves this by inverting the dependency:

  • The downstream project publishes a YAML mapping + a Python plugin shim
  • The FCC framework discovers the plugin at startup via importlib.metadata entry points
  • Neither project imports the other at runtime

This is the same contract pattern used for Anthropic/OpenAI provider plugins, evaluator scorers, and event subscribers.


The PluginProviderProtocol

Every plugin provider must satisfy the runtime-checkable protocol at src/fcc/objectmodel/provider_registry.py:

@runtime_checkable
class PluginProviderProtocol(Protocol):
    def provider_name(self) -> str: ...
    def provider_type(self) -> str: ...
    def is_available(self) -> bool: ...
    def activate(self, config: dict[str, Any]) -> None: ...
    def deactivate(self) -> None: ...
    def health_check(self) -> dict[str, Any]: ...

For vocabularies, provider_type() returns "vocabulary_provider".


Authoring a VocabularyProviderPlugin

The simplest possible plugin:

# myproject/fcc_plugin.py
from fcc.objectmodel.provider_registry import PluginProviderProtocol
from fcc.objectmodel.vocabulary_loader import load_vocabulary_from_yaml
from pathlib import Path

class MyProjectVocabularyProvider:
    def __init__(self) -> None:
        self._loaded = False
        self._mappings = None

    def provider_name(self) -> str:
        return "myproject"

    def provider_type(self) -> str:
        return "vocabulary_provider"

    def is_available(self) -> bool:
        return self._loaded

    def activate(self, config: dict) -> None:
        yaml_path = Path(config.get("mapping_path") or self._default_path())
        self._mappings = load_vocabulary_from_yaml(yaml_path)
        self._loaded = True

    def deactivate(self) -> None:
        self._mappings = None
        self._loaded = False

    def health_check(self) -> dict:
        return {
            "status": "ok" if self._loaded else "inactive",
            "mapping_count": len(self._mappings or []),
        }

    def mappings(self):
        return self._mappings

    def _default_path(self) -> Path:
        return Path(__file__).parent / "data" / "myproject_vocabulary_mappings.yaml"

Register via your package's pyproject.toml:

[project.entry-points."fcc.plugins"]
myproject_vocabulary = "myproject.fcc_plugin:MyProjectVocabularyProvider"

FCC's provider registry will discover the plugin at startup. No FCC code changes needed.


Authoring the YAML Mapping

A vocabulary mapping file contains concepts, synonyms, external identifiers, and relationships. The canonical shape:

vocabulary:
  name: myproject
  version: 1.0.0
  namespace: https://example.org/myproject/ontology#
  source: MyProject v2.3
  license: CC-BY-4.0

concepts:
  - id: myproject:Customer
    label: Customer
    definition: "A party who purchases goods or services."
    synonyms: [Client, Buyer, Subscriber]
    external_ids:
      fibo: "fibo:Party"
      schema_org: "schema:Person"
    broader: myproject:Party
    narrower: [myproject:RetailCustomer, myproject:EnterpriseCustomer]
    examples: ["A household buying groceries"]

Fields external_ids are the bridge to federated projects. When a peer vocabulary declares fibo:Party as a concept, the EntityResolver can walk from myproject:Customer to the FIBO definition via the shared external ID.


The VocabularyMapping Dataclass

load_vocabulary_from_yaml() returns a list of VocabularyMapping frozen dataclasses:

@dataclass(frozen=True)
class VocabularyMapping:
    id: str
    label: str
    definition: str
    synonyms: tuple[str, ...]
    external_ids: Mapping[str, str]
    broader: str | None
    narrower: tuple[str, ...]
    examples: tuple[str, ...]
    namespace: str

These are consumed by:

  • The knowledge graph builder (src/fcc/knowledge/builders.py)
  • The federated entity resolver (src/fcc/federation/resolver.py)
  • The RAG grounding layer (src/fcc/rag/pipeline.py)
  • The object model facade (src/fcc/objectmodel/facade.py)

Validation

Run the strict validator before shipping:

fcc audit vocabulary --strict --provider myproject

The validator checks:

  • YAML parses and matches the schema
  • No duplicate concept IDs
  • Every broader / narrower link resolves
  • Every external_id namespace is known to FCC's federation registry
  • Every concept has a non-empty definition

Findings map to quality gate QG-OBM-VOC-001 (vocabulary completeness).


Testing Conformance

The project ships a conformance test harness that downstream plugin authors can reuse:

import pytest
from fcc.objectmodel.provider_registry import PluginProviderProtocol
from myproject.fcc_plugin import MyProjectVocabularyProvider

def test_protocol_conformance():
    provider = MyProjectVocabularyProvider()
    assert isinstance(provider, PluginProviderProtocol)

def test_activates_cleanly():
    provider = MyProjectVocabularyProvider()
    provider.activate(config={})
    assert provider.is_available()
    health = provider.health_check()
    assert health["status"] == "ok"
    assert health["mapping_count"] > 0

def test_round_trips_yaml(tmp_path):
    provider = MyProjectVocabularyProvider()
    provider.activate(config={})
    mappings = provider.mappings()
    assert all(m.id.startswith("myproject:") for m in mappings)

The v1.2.1 release verified this test battery against the athenium (8/8) and mnemosyne (8/8) providers. Use it as a template.


Reference Implementations

The best way to learn the pattern is to read working code. Three recommended reference plugins:

  • Athenium — 8 concepts, financial-instrument vocabulary, src/fcc/data/objectmodel/athenium_vocabulary_mappings.yaml
  • Mnemosyne — 8 concepts, memory/knowledge vocabulary, src/fcc/data/objectmodel/mnemosyne_vocabulary_mappings.yaml
  • CTO — larger vocabulary mapping against the CTO object model, src/fcc/data/objectmodel/cto_vocabulary_mappings.yaml

Open each YAML, then trace the load path through vocabulary_loader.pyprovider_registry.pyfederation.py.


Plugin Versioning

A vocabulary carries three version fields you must manage:

Field Location Meaning
Package version pyproject.toml Plugin code version
Vocabulary version YAML vocabulary.version Content version
Mapping version Per-concept version (optional) Per-concept revision

Bump the vocabulary version on any concept addition, removal, or definitional change. Synonyms and examples can be added without a version bump.

Semantic-version breaking: concept removal, ID change, broader re-link that changes hierarchy depth. Semantic-version additive: new concept, new synonym, new external_id. Semantic-version patch: definition wording, example addition.


The Vocabulary Authority Hierarchy

When two vocabularies conflict on the same concept, FCC resolves by authority tier:

  1. Tier 1FCC-core canonical (e.g. personas, workflows)
  2. Tier 2 — constellation vertical (e.g. ophiuchus for healthcare)
  3. Tier 3 — downstream project plugins (e.g. athenium)
  4. Tier 4 — ad-hoc user extensions

Higher tiers override lower tiers. Within a tier, conflicts are reported as findings; the auditor (see For Auditors) adjudicates.