On this page
Upgrade to normal๏
๐ก Intermediate ยท Lesson 28
upgrade_to_normal - a lesser conduit GROWS UP in place. Lessers are unnamed child scopes (lesson 07); in a dynamic world one can be promoted to a full named citizen: registered in the world, discoverable by name in the cloud, able to do everything a normal conduit does. The promotion retains what the child already built for disposal, but owns a NEW EMPTY Spellbook: former definitions are no longer visible. Bind the new root's definitions explicitly. Dynamic-only verb: in a static world the same call refuses.
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/28_upgrade_to_normal.py
py -3.14t UX_and_AIX_experiences/02_intermediate/28_upgrade_to_normal.py
Public surface๏
create_lesser_conduit, upgrade_to_normal, cloud name lookup after promotion
Code๏
1"""
2TIER: intermediate (28)
3GOAL: upgrade_to_normal - a lesser conduit GROWS UP in place. Lessers
4 are unnamed child scopes (lesson 07); in a dynamic world one can
5 be promoted to a full named citizen: registered in the world,
6 discoverable by name in the cloud, able to do everything a normal
7 conduit does. The promotion retains what the child already built
8 for disposal, but owns a NEW EMPTY Spellbook: former definitions
9 are no longer visible. Bind the new root's definitions explicitly.
10 Dynamic-only verb: in a static world the same call refuses.
11SURFACE EXERCISED: create_lesser_conduit, upgrade_to_normal,
12 cloud name lookup after promotion
13"""
14import sys
15from pathlib import Path
16
17sys.path.insert(0, str(Path(__file__).parent)) # local helper (see _dynamic_world)
18from _dynamic_world import dynamic_spellbook
19
20import melder as md
21
22
23class Workbench:
24 """Track disposal of an object created before promotion."""
25
26 def __init__(self) -> None:
27 """Start with no cleanup calls so promotion can prove retention."""
28 self.cleanup_calls = 0
29
30 def cleanup(self) -> None:
31 """Record disposal by the scope retaining this creation."""
32 self.cleanup_calls += 1
33
34
35class IndependentWorkbench:
36 """A definition owned only by the newly graduated root's Book."""
37
38
39def main() -> None:
40 """Promote in place, register independent definitions and prove disposal ownership."""
41 book = dynamic_spellbook()
42 book.bind(spell=Workbench, existence="unique_per_conduit", disposal_method_names=["cleanup"])
43 root = book.conjure(dynamic=True, name="factory-floor") # settles
44
45 # An unnamed child scope, working away...
46 worker = root.create_lesser_conduit()
47 bench_before = worker.meld("Workbench")
48
49 try:
50 # ...promoted in place. Its new empty Book is already conjured.
51 worker.upgrade_to_normal(name="worker")
52
53 # It KEPT its object for disposal, without inheriting the old definition.
54 assert bench_before.cleanup_calls == 0
55 try:
56 worker.meld("Workbench")
57 except KeyError:
58 print("new Book has no former Workbench definition")
59 else:
60 raise AssertionError("Graduation must start with an empty Book.")
61 worker.bind(spell=IndependentWorkbench, existence="unique_per_conduit")
62 assert isinstance(worker.meld("IndependentWorkbench"), IndependentWorkbench)
63 print("new root can bind and meld its own definitions")
64
65 # And it is now a discoverable citizen of the world.
66 cloud = root.get_conduit_cloud()
67 assert cloud.get_conduit_by_name("worker") is worker
68 print("promoted conduit found by name:", worker.name)
69 worker.cleanup()
70 assert bench_before.cleanup_calls == 1
71 assert isinstance(root.meld("Workbench"), Workbench)
72 print("new root disposed its retained bench; former root still works")
73 finally:
74 worker.cleanup()
75 root.cleanup()
76 book.cleanup()
77
78
79if __name__ == "__main__":
80 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.