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
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.