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:
- FCC frozen dataclasses (
frozen=True) and CTO slots-based dataclasses (slots=True) can both satisfy the contract without sharing a base class. - Third-party repositories (database wrappers, graph stores, in-memory caches) are eligible without modification.
- 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 CTOCTOModelas aModelFacadeCTORepositoryAdapter— wraps a CTORepository[T]as aRepositoryProtocol[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. Thefcc objectmodel registerCLI 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:
- Create a facade subclass under
src/fcc/objectmodel/or under your own plugin. - Register it via
fcc objectmodel register --namespace <ns> --facade <dotted.path>. - Add a vocabulary-mapping YAML at
src/fcc/data/objectmodel/<ns>_vocabulary_mappings.yaml. - Add a namespace entry to
src/fcc/data/federation/namespace_registry.yaml. - 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.