Skip to content

Exceptions & Utility Modules (exceptions.py, validators.py, serializers.py, factories.py)

Exception Hierarchy (exceptions.py)

exceptions.py is a leaf module — every other module depends on it, it depends on nothing. Every exception documented across this specification is a concrete leaf of the tree below.

QuantsMindError
└── FoundationError
    ├── EntityError
    │   ├── InvalidLifecycleTransitionError
    │   ├── InvalidStateError
    │   ├── ConstraintViolationError
    │   ├── DuplicateIdentityError
    │   ├── DuplicatePropertyError
    │   ├── PropertyNotFoundError
    │   ├── ImmutablePropertyError
    │   ├── DuplicateAttributeError
    │   ├── QmAttributeError            (aliases Attribute-related failures; avoids
    │   │                                 shadowing Python's builtin AttributeError)
    │   │   └── AttributeNotFoundError
    │   ├── RelationshipError
    │   │   └── InvalidCardinalityError
    │   ├── CloneNotSupportedError
    │   └── InvalidComparisonError
    │
    ├── SystemError                     (namespaced as quantsmind SystemError;
    │   │                                 does not shadow Python's builtin)
    │   ├── EntityNotFoundError
    │   ├── DuplicateRelationshipError
    │   └── (reuses ConstraintViolationError, InteractionError from siblings)
    │
    ├── StateError
    │   ├── InvalidStateError            (shared with EntityError — see note below)
    │   ├── IncompatibleStateError
    │   └── StateMergeConflictError
    │
    ├── InteractionError
    │   ├── TransformationError
    │   │   ├── TransformationNotReversibleError
    │   │   └── SchemaMismatchError
    │   └── InvalidInteractionStateError
    │
    ├── IdentityError
    │   └── InvalidIdentityError
    │
    ├── PropertyError
    │   └── ValidationFailedError        (shared with AttributeError — see note below)
    │
    ├── BehaviourError
    │   └── InteractionNotPermittedError
    │
    ├── RelationshipError                (also used standalone by relationship.py)
    │
    ├── ConstraintError
    │   └── InvalidConstraintExpressionError
    │
    ├── LifecycleError
    │   └── InvalidLifecycleTransitionError (shared with EntityError — see note below)
    │
    ├── EventError
    │   ├── UnknownEventTypeError
    │   └── EventBusUnavailableError
    │
    ├── ObservationError
    │   └── InvalidObservationError
    │
    ├── KnowledgeError
    │   ├── InsufficientEvidenceError
    │   └── PredictionNotSupportedError
    │
    ├── SpaceError
    │   ├── DimensionMismatchError
    │   ├── OutOfBoundsError
    │   └── MetricNotImplementedError
    │
    ├── TimeError
    │   └── IncompatibleTimeModeError
    │
    ├── UnsupportedFormatError           (serializers.py — shared across all Serializable classes)
    └── DeserializationError             (serializers.py — shared across all Serializable classes)

Note on shared leaf exceptions. A handful of leaf exceptions (InvalidStateError, ValidationFailedError, InvalidLifecycleTransitionError) are raised by more than one owning module. They are defined once, in the module that most centrally owns the concept (InvalidStateError in state.py, ValidationFailedError in validators.py, and InvalidLifecycleTransitionError in lifecycle.py), and re-exported by the modules that raise them, rather than being redefined per module — this avoids the "duplicate concepts" anti-pattern called out in the founding architecture while keeping each exception co-located with the concept it is most naturally about.

Design rules for this hierarchy: 1. Every exception carries structured context (entity_id, field_name, expected, actual, etc. as applicable) as attributes, not just a message string — required so callers (and the logging/telemetry hooks) can act on failures programmatically. 2. Only leaf exceptions are ever raised directly; FoundationError and the per-concept intermediate classes (EntityError, SystemError, ...) exist purely for except clause granularity. 3. No exception class may cross a package boundary undocumented — every foundation exception is documented in this file precisely so that core, runtime, and every domain package know the full catchable surface.

validators.py

Purpose. Declares the Validator contract (mirrored structurally in protocols.py) and a small library of reusable, composable validation rule descriptors — no domain-specific validation logic.

Contract.

Validator.validate(value: Any) -> ValidationResult
ValidationResult { is_valid: bool, errors: list[ValidationError] }
ValidationError { field: str, message: str, code: str }

Reusable built-in Validator descriptors (architecture only): - TypeValidator(expected_type) — checks isinstance. - RangeValidator(min_=None, max_=None) — numeric bounds. - RegexValidator(pattern) — string pattern matching. - RequiredValidator() — rejects None. - PredicateValidator(predicate: Callable[[Any], bool], message: str) — arbitrary custom rule, still data-free at the class level (the predicate itself is supplied by the caller, not stored as serialized state — see 04-interfaces-protocols.md's discussion of why Constraint.expression must be data-only while a Validator's predicate may be a live callable, since Validators are not required to be Serializable). - ValidatorChain(*validators) — composes multiple Validators, short-circuiting or collecting all errors per a mode flag (FAIL_FAST | COLLECT_ALL).

Used by: Property, Attribute, Entity.validate(), System.validate().

serializers.py

Purpose. Declares the Serializer contract and per-format capability descriptors; implements none of them (concrete implementations are an R0.3+ concern, once a concrete numeric/data backend is chosen).

Contract.

Serializer.serialize(obj: Serializable) -> bytes | str
Serializer.deserialize(data: bytes | str, target_type: type) -> Serializable
Serializer.format: SerializationFormat  (property)
Serializer.is_binary: bool              (property)
Serializer.supports_schema_evolution: bool  (property)

Format capability matrix (architecture-level guidance, not enforced in code):

Format Binary Human-readable Schema evolution Recommended for
JSON No Yes Loose (additive fields) Debugging, REST APIs, default
YAML No Yes Loose Config-adjacent documents
MessagePack Yes No Loose Compact wire transport
Binary (custom) Yes No None (versioned framing required) High-performance internal transport
Protocol Buffers Yes No Strict (schema-first) Cross-language boundary, the future C++/Rust/Java/Go/Julia bridge

Envelope requirement. Every serialized document must include an envelope carrying sdk_version (from constants.SDK_VERSION), type (fully-qualified class name), and format — required so a deserialize() call can dispatch to the correct concrete class and detect version skew.

factories.py

Purpose. Declares factory contracts that centralize construction, guaranteeing invariants that individual class constructors should not have to re-implement themselves.

Contracts.

EntityFactory.create(type_: str, **kwargs) -> Entity
SystemFactory.create(type_: str, **kwargs) -> System
InteractionFactory.create(type_: str, participants: list[Entity], transformation: Transformation, **kwargs) -> Interaction
EventFactory.create(type_: EventType, source_id: str, payload: dict, **kwargs) -> Event

Guaranteed invariants (documented, enforced by the factory, not by the raw class constructor): - Every Entity/System returned has a valid, unique Identity already registered in the target scope. - Every Entity/System returned starts in LifecycleStage.CREATED. - Every Interaction returned has verified participant Behaviour eligibility before returning (fail-fast at construction rather than at apply() time). - Every Event returned has a valid EventType and a populated timestamp.

Registry extension point. factories.py also declares a FactoryRegistry contract (register(type_name, factory), resolve(type_name) -> Factory) so domain packages can register their own Qubit/Portfolio/Molecule factories without foundation needing to know about them — the mechanism plugins.py (in the top-level plugins package, not foundation) will build on in a later release.