On this page

Late binding with SpellContract: runnable example๏ƒ

๐ŸŸก Intermediate ยท Lesson 36

SpellContract - the LATE-BOUND socket. A SpellMap says "resolve this from MY book" and must be satisfiable at conjure. A SpellContract says "someone will hand me this later", and stays OPEN until a linked conduit supplies the provider. That is the difference: SpellMap is a lookup, SpellContract is a PROMISE.

The socket closes with normal verbs only - link, grant, meld. Nothing reaches into another book; the provider conduit GRANTS the spell across the link and the consumer melds as usual.

Contracts are dynamic-mode only. In an automatic world Phase 4 refuses them outright, which is why this lesson settles a dynamic world first.

Before you run๏ƒ

Use the Intermediate guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.

Run the saved script๏ƒ

python UX_and_AIX_experiences/02_intermediate/36_late_binding_with_spellcontract.py
py -3.14t UX_and_AIX_experiences/02_intermediate/36_late_binding_with_spellcontract.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

md.SpellContract(spellframe=..., binding_name=...), conduit.link(...), conduit.add_spell_to_contract(...)

Code๏ƒ

  1"""
  2TIER: intermediate (36)
  3GOAL: SpellContract - the LATE-BOUND socket. A SpellMap says "resolve this
  4      from MY book" and must be satisfiable at conjure. A SpellContract says
  5      "someone will hand me this later", and stays OPEN until a linked
  6      conduit supplies the provider. That is the difference: SpellMap is a
  7      lookup, SpellContract is a PROMISE.
  8
  9      The socket closes with normal verbs only - link, grant, meld. Nothing
 10      reaches into another book; the provider conduit GRANTS the spell across
 11      the link and the consumer melds as usual.
 12
 13      Contracts are dynamic-mode only. In an automatic world Phase 4 refuses
 14      them outright, which is why this lesson settles a dynamic world first.
 15SURFACE EXERCISED: md.SpellContract(spellframe=..., binding_name=...),
 16      conduit.link(...), conduit.add_spell_to_contract(...)
 17"""
 18import sys
 19from pathlib import Path
 20
 21sys.path.insert(0, str(Path(__file__).parent))
 22from _dynamic_world import dynamic_spellbook
 23
 24import melder as md
 25
 26from typing import Protocol
 27
 28
 29class IStore(Protocol):
 30    """The SHAPE the consumer depends on - not the class that provides it."""
 31
 32    def get(self, key: str) -> str: ...
 33
 34
 35class PlatformStore:
 36    """Lives in the platform book. The consumer never imports this."""
 37
 38    def get(self, key: str) -> str:
 39        return f"{key}-ok"
 40
 41
 42class NeedsStore:
 43    """
 44    The socket is declared as a DEFAULT VALUE on the constructor.
 45
 46    Read it as: "I need something shaped like IStore, named 'platform'.
 47    I do not know who provides it and I am not looking it up myself."
 48    """
 49
 50    def __init__(self, store: IStore = md.SpellContract(
 51            spellframe=IStore, binding_name="platform")) -> None:
 52        self.store = store
 53
 54
 55def main() -> None:
 56    # TWO SEPARATE BOOKS - two independent worlds of registration. The
 57    # consumer book has NO PlatformStore in it. That is the whole point:
 58    # if it could resolve the socket locally you would use SpellMap.
 59    platform_book = dynamic_spellbook()
 60    store_id = platform_book.bind(
 61        spell=PlatformStore,
 62        existence="unique",
 63        spellframe=IStore,
 64        binding_name="platform",
 65    )
 66
 67    services_book = dynamic_spellbook()
 68    consumer_id = services_book.bind(spell=NeedsStore, existence="unique")
 69
 70    # Both conjure fine even though the socket is still OPEN. An unfilled
 71    # contract is not a build error - that is exactly what makes it LATE
 72    # binding. A SpellMap that resolved to nothing would have failed here.
 73    platform = platform_book.conjure(dynamic=True, name="platform")
 74    services = services_book.conjure(dynamic=True, name="services")
 75
 76    # THE LINK is the relationship. It grants nothing on its own.
 77    platform.link(services)
 78
 79    # THE GRANT is what actually closes the socket: the borrowing side asks
 80    # for one spell, by id, from a named conduit, at a stated permission.
 81    # Narrow and explicit - not "share everything you have".
 82    assert services.add_spell_to_contract(
 83        spell_id=store_id,
 84        conduit=platform,
 85        permissions="create",
 86    )
 87
 88    # Now meld normally. The consumer never mentions PlatformStore, never
 89    # touches the platform conduit, and never knew who filled the socket.
 90    consumer = services.meld(spell_id=consumer_id)
 91    assert isinstance(consumer, NeedsStore)
 92    assert consumer.store.get("region") == "region-ok"
 93    print("contract socket closed across the link:",
 94          consumer.store.get("region"))
 95
 96    # WHEN TO REACH FOR WHICH:
 97    #   SpellMap      - the provider is in MY book. Resolve it now.
 98    #   SpellContract - the provider arrives from SOMEONE ELSE'S book, later.
 99    # If you find yourself binding a placeholder just to satisfy a SpellMap,
100    # you wanted a contract.
101
102    services.cleanup()
103    platform.cleanup()
104
105
106if __name__ == "__main__":
107    main()

Check the outcome๏ƒ

The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.

More intermediate examples ยท Level guide

API contracts๏ƒ