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

Download this collection ยท Source on GitHub

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.

More expert examples ยท Level guide