On this page

SpellContract๏ƒ

Use md.SpellContract from the public package namespace.

Implementation source

class SpellContract(spell: Any | None = None, *, spellframe: Any | None = None, binding_name: str | None = None, spell_override: dict | list | tuple | None = None)[source]๏ƒ

Declarative late-binding contract socket for dynamic conduit linking.

SpellContract is not a generic in-spellbook DI placeholder like SpellMap. It declares that a parameter or field should eventually be satisfied by some spell matching a frame/binding contract, even if that provider does not exist in the current Spellbook or Conduit yet.

In the runtime model, this descriptor marks a contract-bearing dependency:

  • during conjure/analysis, SpellCrafter records the socket as an unresolved contract edge rather than resolving it eagerly

  • during dynamic conduit linking, ConduitWard/linking flows look for a provider in another conduit that satisfies the same (frame, binding) identity

  • once a provider is linked, later validation phases rerun so the consumer graph is rebuilt against that now-satisfied contract

This is why the descriptor is dynamic-mode only in practice. Automatic mode expects one self-contained Spellbook graph; SpellContract exists for cross-conduit, post-conjure wiring that is intentionally deferred.

Relationship to neighboring descriptor types:

  • SpellMap expresses normal DI intent inside the current resolution world and is expected to resolve relative to the current spellbook graph

  • SpellContract expresses a late-bound dependency hole whose provider may arrive from another conduit later

Typical usage:

class ReportingService:
    def __init__(
        self,
        auth=SpellContract(
            spellframe=IAuthService,
            binding_name="primary",
        ),
    ):
        self._auth = auth

When the reporting conduit is conjured, the socket remains unresolved but explicitly declared. When a provider conduit is linked later, the linker can satisfy the contract and trigger revalidation of the reporting lineage.

Contract:

  • Pure intent object; it does not perform linking or resolution itself.

  • Used to describe cross-conduit dependency identity.

  • Cleanable and invalid after cleanup.

  • Should not be subclassed or used as a substitute for SpellMap in ordinary in-conduit DI.

Threading:

Value-shaped and effectively immutable after construction: the four slots are set in __init__ and read thereafter. No lock is taken, because a descriptor written into a constructor default is shared read-only across every resolution that reads it.

Lifecycle / Cleanup:

Cleanable. The descriptor lives as long as the class default that declares it, which in practice is the lifetime of the defining module.

Registration:

MELDER KERNEL, USER-INSTANTIATED but NOT user-bindable. A user authors SpellContract(spellframe=IAuthService) in their own constructor default; only binding the SpellContract CLASS is refused, which would be meaningless because a socket is a declaration of absence, not a service.

Subsystem Context:

The late-binding half of the descriptor pair. SpellMap resolves inside the current resolution world; this one declares an edge the graph deliberately CANNOT close yet. Phase 1 classifies it as the SPELL_CONTRACT parameter shape, Phase 4 validates the socket, and ConduitWard linking is what eventually satisfies it.

System Context:

The severity split in Phase 4 is the whole design in miniature. In AUTOMATIC mode an unsatisfied contract socket is an ERROR, because that mode promises one self-contained graph - a hole there can never be filled and the world is simply wrong. In DYNAMIC mode a missing provider is only a WARNING, because being unsatisfied is the expected intermediate state: the provider conduit may not be linked yet. The same asymmetry explains why linking is not merely a registry write. Satisfying a contract changes what the consumer graph resolves to, so SpellSystemStates marks contract dependents dirty and the consumer lineage re-runs its resolution phases against the now-satisfied edge. A contract is therefore a REVALIDATION TRIGGER, not just a lookup key - which is also why SpellContract requires at least a spell or a spellframe: with neither there is no identity for a future linker to match against, and the socket could never be closed.

AGENT_ACCESS: public

AGENT_PURPOSE:

access: public. Declares a LATE-BOUND dependency hole for dynamic conduit linking. Write it as a constructor default when the provider will arrive from another conduit. Requires at least spell or spellframe. Unsatisfied is an ERROR in automatic mode, a warning in dynamic.

cleanup() None[source]๏ƒ

Release descriptor references and invalidate the socket object.

Contract:

  • Idempotent and safe to call multiple times.

  • Clears any mutable override payload before dropping references.

  • After cleanup the descriptor should be treated as unusable and callers should rely on check_cleaned() before reading it.

Returns:

None.

property lookup_triplet: tuple[Any, Any | None, str | None]๏ƒ

Return the raw contract identity tuple.

This is the descriptor shape consumed by the dynamic contract pipeline when it captures unresolved contract sockets during analysis and later tries to match them against linked provider spells.

Contract:

  • Returns the RAW, AS-SUPPLIED triplet - the spell, spellframe and binding name exactly as the caller gave them, WITHOUT normalization.

  • This is NOT the registry key. Use canonical_key when you need the normalized identity the spellbook actually indexes by; the two can differ for the same SpellContract.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

(spell, spellframe, binding_name) exactly as stored on the descriptor.

Return type:

tuple[Any, Optional[Any], Optional[str]]

Notes

  • For frame-only contracts (spell is None), only spellframe and binding_name define the contract identity.

  • When spell is present, it is part of the contract descriptor but does not imply immediate resolution; the provider may live in another conduit.

  • If a binding name was provided at construction time, it is normalized for case-insensitive matching.

property canonical_key: Tuple[str, str]๏ƒ

Return the normalized contract identity used by runtime lookup tables.

In practice this is the stable (frame_key, binding_key) pair used to:

  • index contract sockets in Spellbook / SpellSystemStates style maps

  • match a consumer contract hole against provider spells during conduit linking

Contract:

  • NORMALIZED identity: it runs the raw triplet through the shared key normalizer, so equivalent-but-differently-spelled inputs collapse to the same key. This is what the spellbook indexes by.

  • Recomputed on every access rather than cached, so it always reflects the current field values.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

Normalized (frame_key, binding_key) pair.

Return type:

Tuple[str, str]

property spell_key: Tuple[str, str]๏ƒ

Compatibility alias for canonical_key.

Contract:

  • ALIAS for canonical_key, kept for call-site readability. Identical behaviour - it is the NORMALIZED key, not the raw triplet.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

The same normalized contract identifier returned by canonical_key.

Return type:

Tuple[str, str]

Topic reference ยท Full contents