On this page
Composing a version in the workshop๏
๐ต Expert ยท Lesson 30
THE HALF OF A MERGE THAT JOIN REFUSES TO DO, on code the room actually generated. Expert 23 said it outright: "Reconciliation-by- content is not a join concern: compose in the codegen workshop, register the multi-parent result, then join." This is that workshop, and the material is real codegen output - not classes typed into this file.
A record that merged content FOR you would be guessing about your source. So melder splits the act in two: you COMPOSE (here), and the record BOOKS the outcome (expert 23). Nothing in between.
THE COMPOSITION VERB research_synthesize(base_spell_id, donor_spell_id, take_functions=, take_classes=, stage_ancestry=False) Take named top-level functions/classes from the DONOR's root module, splice them into the BASE's, and hand back the composed source with a full foresight preview. Nothing executes, binds or records - it is a candidate, not a version.
SYNTHESIS IS MODULE-GRAIN. The engine says so itself: it "splices ONE version's module world". Each id resolves to its ROOT MODULE text, and take_functions / take_classes name TOP-LEVEL parts of that module. A METHOD is not a top-level function. Asking for one raises, and the refusal NAMES what the donor actually carries - which is how you discover the grain if you guessed wrong.
WHICH IS WHY THIS LESSON GENERATES ITS TWO MODULES. Two classes typed into ONE example file resolve to the SAME module, so every selection is a part replaced by itself and "added" is unreachable - the splice degenerates into a no-op that proves nothing. Real composition needs two module worlds, so the room writes them: validate_codegen -> materialize_codegen -> import -> bind That is the documented loop - materialize's own contract calls binding the class inside the new module the step that "closes the codegen -> synthmodule -> bind -> crystal loop".
AND GENERATED CODE IS THE MORE RELIABLE SOURCE, not the exotic one: "synthetic module sources are ALWAYS harvested; user module text rides the opt-in retention lane". The material a room wrote is the material it can always read back.
AND THE DIFF DEFAULT IS KIND-AWARE, not a single fallback. research_diff(left, right, strategy=None) With no strategy the room asks what you handed it. Two SPELLS get "structural" - the room's reasoning layer - pinned for them. Two COMPOSITIONS get nothing pinned at all: the room passes no strategy and the engine's own "members" default answers. One verb, two vocabularies, selected by KIND. ("source" is whole-module text, "parts" is per-class/function grain; both are always yours by asking.) A DEFAULT IS A KINDNESS FOR SILENCE, NEVER AN OVERRIDE. Name a strategy and it travels down untouched - an unknown one surfaces the engine's KeyError rather than being quietly answered by a different question than the one asked.
THE MINT IS A SEPARATE, ONE-SHOT STAMP research_stage_ancestry([base, donor]) research_clear_staged_ancestry() Staging says "the next FRESH world entry has these parents". It is consumed ONCE and it is ambient, not attached to any particular call. A REDISCOVERY DOES NOT CONSUME IT - identical content re-entering the world is not the synthesized candidate arriving. AND THE STAMP SURVIVES UNTIL USED OR CLEARED. No scope ends it, which is why clear_staged_ancestry exists: abandon a composition and you must clear, or the next unrelated bind inherits parents it never had.
Before you run๏
Use the Expert 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/04_expert/30_composing_a_version_in_the_workshop.py
py -3.14t UX_and_AIX_experiences/04_expert/30_composing_a_version_in_the_workshop.py
Public surface๏
validate_codegen / materialize_codegen / research_synthesize / research_stage_ancestry / research_clear_staged_ancestry / research_diff
Code๏
1"""
2TIER: expert (30)
3GOAL: THE HALF OF A MERGE THAT JOIN REFUSES TO DO, on code the room
4 actually generated. Expert 23 said it outright: "Reconciliation-by-
5 content is not a join concern: compose in the codegen workshop,
6 register the multi-parent result, then join." This is that
7 workshop, and the material is real codegen output - not classes
8 typed into this file.
9
10 A record that merged content FOR you would be guessing about your
11 source. So melder splits the act in two: you COMPOSE (here), and
12 the record BOOKS the outcome (expert 23). Nothing in between.
13
14 THE COMPOSITION VERB
15 research_synthesize(base_spell_id, donor_spell_id,
16 take_functions=, take_classes=,
17 stage_ancestry=False)
18 Take named top-level functions/classes from the DONOR's root module,
19 splice them into the BASE's, and hand back the composed source with
20 a full foresight preview. Nothing executes, binds or records - it is
21 a candidate, not a version.
22
23 SYNTHESIS IS MODULE-GRAIN. The engine says so itself: it "splices
24 ONE version's module world". Each id resolves to its ROOT MODULE
25 text, and `take_functions` / `take_classes` name TOP-LEVEL parts of
26 that module. A METHOD is not a top-level function. Asking for one
27 raises, and the refusal NAMES what the donor actually carries -
28 which is how you discover the grain if you guessed wrong.
29
30 WHICH IS WHY THIS LESSON GENERATES ITS TWO MODULES. Two classes
31 typed into ONE example file resolve to the SAME module, so every
32 selection is a part replaced by itself and "added" is unreachable -
33 the splice degenerates into a no-op that proves nothing. Real
34 composition needs two module worlds, so the room writes them:
35 validate_codegen -> materialize_codegen -> import -> bind
36 That is the documented loop - materialize's own contract calls
37 binding the class inside the new module the step that "closes the
38 codegen -> synthmodule -> bind -> crystal loop".
39
40 AND GENERATED CODE IS THE MORE RELIABLE SOURCE, not the exotic one:
41 "synthetic module sources are ALWAYS harvested; user module text
42 rides the opt-in retention lane". The material a room wrote is the
43 material it can always read back.
44
45 AND THE DIFF DEFAULT IS KIND-AWARE, not a single fallback.
46 research_diff(left, right, strategy=None)
47 With no strategy the room asks what you handed it. Two SPELLS get
48 "structural" - the room's reasoning layer - pinned for them. Two
49 COMPOSITIONS get nothing pinned at all: the room passes no strategy
50 and the engine's own "members" default answers. One verb, two
51 vocabularies, selected by KIND. ("source" is whole-module text,
52 "parts" is per-class/function grain; both are always yours by
53 asking.)
54 A DEFAULT IS A KINDNESS FOR SILENCE, NEVER AN OVERRIDE. Name a
55 strategy and it travels down untouched - an unknown one surfaces the
56 engine's KeyError rather than being quietly answered by a different
57 question than the one asked.
58
59 THE MINT IS A SEPARATE, ONE-SHOT STAMP
60 research_stage_ancestry([base, donor])
61 research_clear_staged_ancestry()
62 Staging says "the next FRESH world entry has these parents". It is
63 consumed ONCE and it is ambient, not attached to any particular
64 call. A REDISCOVERY DOES NOT CONSUME IT - identical content
65 re-entering the world is not the synthesized candidate arriving.
66 AND THE STAMP SURVIVES UNTIL USED OR CLEARED. No scope ends it,
67 which is why `clear_staged_ancestry` exists: abandon a composition
68 and you must clear, or the next unrelated bind inherits parents it
69 never had.
70SURFACE EXERCISED: validate_codegen / materialize_codegen /
71 research_synthesize / research_stage_ancestry /
72 research_clear_staged_ancestry / research_diff
73VERIFY: rides the owner's 3.14t harness; asserts are the contract.
74"""
75import importlib
76
77import melder as md
78
79
80FRAME = "workshop-world"
81BASE_MODULE = "workshop_generated_base"
82DONOR_MODULE = "workshop_generated_donor"
83
84# THE BASE the room writes. One class, one top-level part.
85BASE_SOURCE = '''"""Generated base module."""
86
87
88class ReportBase:
89 def __init__(self) -> None:
90 self.title = "base"
91
92 def render(self) -> str:
93 return "base:" + self.title
94
95
96def render_header() -> str:
97 return "== report =="
98'''
99
100# THE DONOR. Same header (so a selection can REPLACE), one EXTRA
101# top-level part (so a selection can ADD), and one METHOD that
102# take_functions cannot see.
103DONOR_SOURCE = '''"""Generated donor module."""
104
105
106class ReportDonor:
107 def __init__(self) -> None:
108 self.title = "donor"
109
110 def render(self) -> str:
111 return "donor:" + self.title
112
113 def summarise(self) -> str:
114 return "a METHOD - invisible to take_functions"
115
116
117def render_header() -> str:
118 return "== donor report =="
119
120
121def render_footer() -> str:
122 return "-- end of report --"
123'''
124
125
126def _show(label: str, value: object) -> None:
127 if isinstance(value, dict):
128 print(" %-24s -> dict, keys: %s" % (label, sorted(value)[:5]))
129 elif isinstance(value, list):
130 print(" %-24s -> list, %d row(s)" % (label, len(value)))
131 else:
132 print(" %-24s -> %s" % (label, type(value).__name__))
133
134
135def main() -> None:
136 crystallizer = md.Crystallizer()
137 crystallizer.activate(
138 md.CrystallizerConfigurationBuilder().with_defaults().activate(),
139 )
140 research = md.MutationResearch()
141 configuration = research.create_configuration()
142 configuration.with_defaults().activate()
143 research.activate(configuration)
144
145 spellbook_configuration = (
146 md.SpellbookConfiguration(FRAME).with_defaults().finalize()
147 )
148 book = md.Spellbook(aetheric_frame=FRAME,
149 configuration=spellbook_configuration)
150 book.configure_aether_frame(
151 system_state="dynamic",
152 disposal=None,
153 disposal_method_names=None,
154 rift_enabled=True,
155 ai_native=True,
156 )
157
158 # AN EMPTY FRAME IS A REAL FRAME. `configure_aether_frame` declares the
159 # frame's LAW; `conjure` REALIZES it by giving it a root conduit, and
160 # that realization is what publishes it to the Nexus. Publication is
161 # gated on `rift_enabled` ALONE - the spell loop it runs iterates
162 # whatever the book holds, including nothing. So the frame below is
163 # conjured EMPTY and is immediately linkable; spells are cargo, not a
164 # precondition, and everything bound after this arrives incrementally.
165 book.conjure(name="workshop-root")
166
167 nexus = md.Nexus()
168 system_configuration = nexus.create_configuration()
169 system_configuration.with_rift_creation_enabled(True)
170 system_configuration.with_allowed_target_frame_names([FRAME])
171 nexus.activate(system_configuration)
172 rift_configuration = nexus.create_rift_configuration()
173 rift_configuration.with_space_type("codegen")
174 rift = nexus.create_rift(configuration=rift_configuration,
175 rift_name="workshop")
176 rift.mark_active()
177 rift.create_frame_link(FRAME)
178 commands = rift.space.command_system
179
180 # THE ROOM WRITES BOTH MODULE WORLDS. Validation gates materialization;
181 # a rejected verdict registers and publishes NOTHING.
182 print("THE ROOM WRITES ITS OWN MATERIAL:")
183 for module_name, source in ((BASE_MODULE, BASE_SOURCE),
184 (DONOR_MODULE, DONOR_SOURCE)):
185 verdict = commands.validate_codegen(source, frame_name=FRAME)
186 kept = commands.materialize_codegen(
187 source, module_name=module_name, frame_name=FRAME,
188 )
189 assert kept["materialized"] is True, kept
190 print(" %-26s validate(%s) -> materialize(ok)"
191 % (module_name, type(verdict).__name__))
192
193 # The import hook is installed, so plain import resolves onto the
194 # world object. THIS is what gives two spells two module worlds.
195 base_module = importlib.import_module(BASE_MODULE)
196 donor_module = importlib.import_module(DONOR_MODULE)
197
198 # DISTINCT CLASS NAMES, and that is not cosmetic. These are two
199 # INDEPENDENT binds, so both spells are visible at once - and two
200 # visible spells sharing a name make `meld("SpellName")` ambiguous,
201 # which the structural validator refuses outright. A distinct
202 # binding_name alone does NOT settle it; the name itself has to
203 # resolve, or the pair needs a spellframe. (Two versions on ONE
204 # lineage are exempt - they are one spell, not two.)
205 base = book.bind(spell=base_module.ReportBase, existence="unique",
206 permissions="create", binding_name="workshop-base")
207 donor = book.bind(spell=donor_module.ReportDonor, existence="unique",
208 permissions="create", binding_name="workshop-donor")
209 print(" bound from the generated modules -> custody minted")
210 print(" base:", base[:12], " donor:", donor[:12])
211
212 # THE DEFAULT IS KIND-AWARE, not a single fallback. Asked for a diff
213 # with no strategy, the room checks whether BOTH sides are
214 # compositions. If they are not, it pins "structural" - its reasoning
215 # layer - and calls down. If they ARE, it passes NO strategy at all and
216 # lets the engine's own "members" default answer. One verb, two
217 # vocabularies, chosen by what you handed it.
218 print()
219 print("DIFF IS DERIVED, and its default is KIND-AWARE:")
220 print(" spell pair -> the room pins `structural`")
221 print(" composition pair -> the room stands back; engine says `members`")
222 try:
223 _show("diff(default)", commands.research_diff(base, donor))
224 _show("diff(source)",
225 commands.research_diff(base, donor, strategy="source"))
226 _show("diff(parts)",
227 commands.research_diff(base, donor, strategy="parts"))
228 except RuntimeError as custody:
229 print(" custody read REFUSED:", str(custody)[:100])
230 return
231 print(" these two are spells, so `structural` was chosen FOR them")
232
233 # AN EXPLICIT ASK IS NEVER REROUTED. The room pins a default only when
234 # you supplied none; the moment you name a strategy it goes down
235 # untouched, and an unknown name surfaces the engine's own KeyError
236 # rather than being quietly answered by a different question.
237 try:
238 commands.research_diff(base, donor, strategy="no-such-strategy")
239 raise AssertionError("expected the engine's KeyError")
240 except KeyError as unknown:
241 print(" unknown strategy -> KeyError:", str(unknown)[:70])
242 print(" a default is a KINDNESS FOR SILENCE, never an override")
243
244 # THE GRAIN, learned the way the engine teaches it. `summarise` is a
245 # METHOD on the donor's Report, not a top-level function.
246 print()
247 print("THE WORKSHOP - compose, do not merge:")
248 try:
249 commands.research_synthesize(base, donor,
250 take_functions=["summarise"])
251 raise AssertionError("a method is not a top-level function")
252 except ValueError as grain:
253 print(" take_functions=['summarise'] REFUSED:")
254 print(" ", str(grain)[:112])
255 print(" the refusal NAMES the donor's real top-level parts")
256
257 # TWO MODULE WORLDS MEAN BOTH ACTIONS ARE REACHABLE.
258 replaced = commands.research_synthesize(
259 base, donor, take_functions=["render_header"],
260 )
261 assert replaced["base_module"] != replaced["donor_module"], (
262 "the whole point of generating two modules"
263 )
264 assert [row["action"] for row in replaced["selections"]] == ["replaced"]
265 print(" take_functions=['render_header'] -> REPLACED (base had one)")
266
267 added = commands.research_synthesize(
268 base, donor, take_functions=["render_footer"],
269 )
270 assert [row["action"] for row in added["selections"]] == ["added"]
271 assert "render_footer" in str(added["composed_source"])
272 _show("synthesize", added)
273 print(" take_functions=['render_footer'] -> ADDED (base had none)")
274 print(" 'added' is only reachable across TWO module worlds, which is")
275 print(" why this lesson generates them instead of typing them here")
276 print(" composed source + preview returned. NOTHING executed, bound")
277 print(" or recorded - a candidate is not a version")
278
279 # THE ANCESTRY STAMP - ambient, one-shot, survives until used.
280 print()
281 print("THE ANCESTRY STAMP:")
282 commands.research_stage_ancestry([base, donor])
283 print(" staged [base, donor] for the NEXT fresh world entry")
284 commands.research_clear_staged_ancestry()
285 print(" cleared without consuming - abandoning a composition without")
286 print(" this is how a later, innocent bind acquires false parents")
287
288 staged = commands.research_synthesize(
289 base, donor, take_functions=["render_footer"], stage_ancestry=True,
290 )
291 assert staged["ancestry_staged"] is True
292 print(" stage_ancestry=True stages [base, donor] in the same call")
293 commands.research_clear_staged_ancestry()
294
295 print()
296 print("compose in the workshop; the record books the outcome")
297 print("a version-control system that merged your source would be")
298 print("guessing, and this one refuses to guess")
299
300
301if __name__ == "__main__":
302 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.