Skip to content

Object Model Overview

FCC ships a first-class object-model abstraction under src/fcc/objectmodel/ that generalises the CTO Object Model's repository + facade pattern into a domain-agnostic contract. Any frozen dataclass with an id and a to_dict() method participates; any repository with add, get_by_id, get_all, count, __iter__, __len__, __contains__ participates. No inheritance, no forced base class.

This chapter explains what the abstraction contains, why it uses Protocols over ABCs, and how to extend it for your own ecosystem project.

The four primitives

The abstraction has four core primitives:

Primitive File Purpose
DomainEntity base.py Structural contract for any entity
RepositoryProtocol[T] base.py Structural contract for storage
ModelFacade facade.py Abstract base for high-level access
EvolutionStage evolution.py Maturity classifier for assessments

The diagram below shows how the primitives compose. Facades hold repositories; repositories hold entities; entities satisfy the DomainEntity protocol. The EvolutionStage enum is orthogonal — it classifies an ObjectModelAssessment, which is produced from the facade on demand.

classDiagram
    class DomainEntity {
      <<Protocol>>
      +id: int | str
      +to_dict() dict
    }
    class RepositoryProtocol~T~ {
      <<Protocol>>
      +add(entity)
      +get_by_id(id) T | None
      +get_all() list~T~
      +count() int
    }
    class ModelFacade {
      <<abstract>>
      +name: str
      +version: str
      +stats() dict
      +search(query, limit) list
      +get_full(entity_id) dict | None
    }
    class EvolutionStage {
      <<enum>>
      FOUNDATIONAL
      STRUCTURED
      SEMANTIC
      FEDERATED
    }
    class ObjectModelAssessment {
      +model_name: str
      +dimensions: tuple
      +stage: EvolutionStage
      +gap_to_next() float
    }

    ModelFacade o--> "n" RepositoryProtocol : aggregates
    RepositoryProtocol o--> "n" DomainEntity : holds
    ObjectModelAssessment --> EvolutionStage : classified as
    ModelFacade ..> ObjectModelAssessment : produces

Why Protocol instead of ABC

The abstraction deliberately uses typing.Protocol with runtime_checkable for the entity and repository contracts. This choice is documented in src/fcc/objectmodel/base.py and was made so that:

  1. FCC frozen dataclasses (frozen=True) and CTO slots-based dataclasses (slots=True) can both satisfy the contract without sharing a base class.
  2. Third-party repositories (database wrappers, graph stores, in-memory caches) are eligible without modification.
  3. Test doubles do not need to inherit — a minimal stub with the right attributes is enough.

This is structural subtyping in the mypy / pyright sense. At runtime, isinstance(obj, RepositoryProtocol) works because the protocol is runtime_checkable.

Subclassing is reserved for ModelFacade, which is an abstract base with real behaviour (to_dict, __repr__) and three abstract methods.

Worked example — a minimal facade

The package ships a worked example at src/fcc/objectmodel/examples.py. The example has two entity kinds (Person, Organization), two repositories, and one facade.

from fcc.objectmodel.examples import create_sample_model

model = create_sample_model()
print(model.stats())          # {'persons': 3, 'organizations': 2}
print(model.search("alice"))  # list[Person | Organization]
print(model.get_full(1))      # dict with person + org + memberships

The facade's get_full is the integration point — it is what the CLI dashboards, simulation engine, and RAG pipeline all call when they need a fully-assembled view of an entity.

The CTO bridge

src/fcc/objectmodel/cto_bridge.py provides two adapters:

  • CTOFacadeAdapter — wraps a CTO CTOModel as a ModelFacade
  • CTORepositoryAdapter — wraps a CTO Repository[T] as a RepositoryProtocol[T]

These are opt-in. cto_available() returns False when the cto package is not installed, and all adapters raise a clear ImportError with a hint to run pip install fcc[cto].

The bridge is the only place FCC knows about CTO specifically. All other ecosystem bridges (AOME, Constellation) follow the same adapter pattern — see aome_bridge.py and constel_bridge.py.

EvolutionStage and assessments

The EvolutionStage enum has four values with threshold-based classification:

_STAGE_THRESHOLDS = [
    (EvolutionStage.FEDERATED, 0.85),
    (EvolutionStage.SEMANTIC, 0.65),
    (EvolutionStage.STRUCTURED, 0.40),
    (EvolutionStage.FOUNDATIONAL, 0.0),
]

An ObjectModelAssessment takes a tuple of DimensionScore entries, computes the mean, and classifies. The helper gap_to_next() returns the distance to the next threshold, which is useful for dashboards and CI gates.

Assessments are produced by src/fcc/objectmodel/assessment_runner.py which knows how to traverse a facade and score common dimensions (entity coverage, attribute richness, cross-reference density, vocabulary resolvability, federation readiness).

Unified facade and provider registry

Two higher-level constructs sit on top of the primitives:

  • UnifiedFacade (unified_facade.py) — a facade-of-facades that federates across ecosystem projects. This is what the federation engine uses.
  • ProviderRegistry (provider_registry.py) — the lookup point for which facade serves which namespace. The fcc objectmodel register CLI writes to this registry.

The unified facade is what makes cross-project operations feel like single-project operations: unified.get_full("paom:PORT-001") resolves through the PAOM facade while unified.get_full("fcc:BC") resolves through the FCC facade.

Extending for your project

To add a new ecosystem to the object-model layer:

  1. Create a facade subclass under src/fcc/objectmodel/ or under your own plugin.
  2. Register it via fcc objectmodel register --namespace <ns> --facade <dotted.path>.
  3. Add a vocabulary-mapping YAML at src/fcc/data/objectmodel/<ns>_vocabulary_mappings.yaml.
  4. Add a namespace entry to src/fcc/data/federation/namespace_registry.yaml.
  5. Run fcc objectmodel assess --namespace <ns> and confirm the stage you expect.

See Vocabulary Mappings for step 3 and Federation Patterns for steps 4–5.

See also