On this page

Notch swapping a live object๏ƒ

๐Ÿ”ต Expert ยท Lesson 17

LINEAGES, AND A HOT SWAP TAKEN END TO END. An index is a version LINEAGE with exactly one SELECTED member. This lesson builds one, stages a second version onto it, shows that staging changes nothing about what resolves - which is why the swap is a two-step act - then takes the swap by the new version's spell_id, melds it, and notches back. Everything runs on public calls.

THE THREE VERBS

book.bind(...) v1 active on a fresh index conduit.bind_inactive(...) v2 PARKED on the same index; returns v2's spell_id conduit.notch_spell( v2 becomes the active member spell_index=index, spell_id=v2_id)

NAME THE VERSION BY ITS SPELL_ID. A spell_id names one version, and the public spell_id->object lookups (get_spell_by_spell_id, find_spell_by_spell_id) answer exactly that version, active or parked (owner ruling 2026-10-17; until 0.2.8253 they answered a parked spell_id with the ACTIVE member). notch_spell takes the spell_id too and resolves the exact version inside the call. The Spellbook is still not the surface you notch from - the conduit's verb does the resolving (owner ruling 2026-10-16; until then the verb took only the parked OBJECT, no public door handed one out, and this lesson stopped at staging).

WHAT STAGING ACTUALLY BUYS YOU bind_inactive puts a real, bound, owned spell into the lineage and leaves the selection alone. The candidate is inert and unmeldable until something promotes it. That separation is what lets a swap be PREPARED long before it is TAKEN - the expensive, failure-prone half (compiling and registering a new version) happens while the old version is still serving.

AND WHAT A NOTCH DOES WHEN IT HAPPENS 0. Checks the version: it must belong to the conduit's Spellbook and to the index you passed, or the call raises ValueError before anything moves. 1. Parks the outgoing active spell off the four active owned maps, then TEARS DOWN ITS CREATION CONTEXT so the door epoch bumps and the warm fast-door cannot serve the stale spell. 2. Promotes the parked candidate into the active maps. 3. Repoints the index pointer and the framewide binding signature, old spell_id -> new spell_id. 4. Re-registers the index gated + dirty, so revalidation recompiles LAZILY on the next resolve rather than inside the window. All of it happens under one seal, and the sealed conduits' creation gates are quiesced for the duration, so no in-flight meld can straddle the repoint. You do not arrange any of that; the verb does, the same way link does in expert 16.

THE OUTGOING VERSION IS NOT DESTROYED. It stays a member of the lineage, parked, which is why a rollback is structurally the same act as the swap - a notch back by its spell_id - rather than a restore.

MIND THE LAYER WHEN YOU READ THE SOURCE. _apply_notch says contracted borrowers are "NOT yet fanned out HERE (owner-side only)". That HERE is load-bearing: the seam is the local switch, and Conduit.notch_spell one layer up does walk the links. A statement read out of its scope becomes a false one.

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/17_notch_swapping_a_live_object.py
py -3.14t UX_and_AIX_experiences/04_expert/17_notch_swapping_a_live_object.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

Conduit.bind_inactive, Conduit.notch_spell(spell_id=...), Conduit.meld, SpellIndex.selected_spell_id / spells_in_index, Conduit.get_spell_by_spell_id

Code๏ƒ

  1"""
  2TIER: expert (17)
  3GOAL: LINEAGES, AND A HOT SWAP TAKEN END TO END. An index is a
  4      version LINEAGE with exactly one SELECTED member. This lesson
  5      builds one, stages a second version onto it, shows that staging
  6      changes nothing about what resolves - which is why the swap is a
  7      two-step act - then takes the swap by the new version's spell_id,
  8      melds it, and notches back. Everything runs on public calls.
  9
 10      THE THREE VERBS
 11
 12        book.bind(...)                v1 active on a fresh index
 13        conduit.bind_inactive(...)    v2 PARKED on the same index;
 14                                      returns v2's spell_id
 15        conduit.notch_spell(          v2 becomes the active member
 16            spell_index=index, spell_id=v2_id)
 17
 18      NAME THE VERSION BY ITS SPELL_ID. A spell_id names one version,
 19      and the public spell_id->object lookups (`get_spell_by_spell_id`,
 20      `find_spell_by_spell_id`) answer exactly that version, active or
 21      parked (owner ruling 2026-10-17; until 0.2.8253 they answered a
 22      parked spell_id with the ACTIVE member). `notch_spell` takes the
 23      spell_id too and resolves the exact version inside the call. The
 24      Spellbook is still not the surface
 25      you notch from - the conduit's verb does the resolving (owner
 26      ruling 2026-10-16; until then the verb took only the parked
 27      OBJECT, no public door handed one out, and this lesson stopped
 28      at staging).
 29
 30      WHAT STAGING ACTUALLY BUYS YOU
 31      `bind_inactive` puts a real, bound, owned spell into the lineage
 32      and leaves the selection alone. The candidate is inert and
 33      unmeldable until something promotes it. That separation is what
 34      lets a swap be PREPARED long before it is TAKEN - the expensive,
 35      failure-prone half (compiling and registering a new version)
 36      happens while the old version is still serving.
 37
 38      AND WHAT A NOTCH DOES WHEN IT HAPPENS
 39        0. Checks the version: it must belong to the conduit's
 40           Spellbook and to the index you passed, or the call raises
 41           ValueError before anything moves.
 42        1. Parks the outgoing active spell off the four active owned
 43           maps, then TEARS DOWN ITS CREATION CONTEXT so the door epoch
 44           bumps and the warm fast-door cannot serve the stale spell.
 45        2. Promotes the parked candidate into the active maps.
 46        3. Repoints the index pointer and the framewide binding
 47           signature, old spell_id -> new spell_id.
 48        4. Re-registers the index gated + dirty, so revalidation
 49           recompiles LAZILY on the next resolve rather than inside the
 50           window.
 51      All of it happens under one seal, and the sealed conduits' creation
 52      gates are quiesced for the duration, so no in-flight meld can
 53      straddle the repoint. You do not arrange any of that; the verb
 54      does, the same way `link` does in expert 16.
 55
 56      THE OUTGOING VERSION IS NOT DESTROYED. It stays a member of the
 57      lineage, parked, which is why a rollback is structurally the same
 58      act as the swap - a notch back by its spell_id - rather than a
 59      restore.
 60
 61      MIND THE LAYER WHEN YOU READ THE SOURCE. `_apply_notch` says
 62      contracted borrowers are "NOT yet fanned out HERE (owner-side
 63      only)". That HERE is load-bearing: the seam is the local switch,
 64      and `Conduit.notch_spell` one layer up does walk the links. A
 65      statement read out of its scope becomes a false one.
 66SURFACE EXERCISED: Conduit.bind_inactive, Conduit.notch_spell(spell_id=...),
 67                   Conduit.meld, SpellIndex.selected_spell_id /
 68                   spells_in_index, Conduit.get_spell_by_spell_id
 69VERIFY: RUN GREEN 2026-10-17 on 3.14t, GIL off and on.
 70"""
 71import melder as md
 72
 73
 74class HotSwapPricingV1:
 75    def __init__(self) -> None:
 76        self.rate = 100
 77
 78
 79class HotSwapPricingV2:
 80    def __init__(self) -> None:
 81        self.rate = 250
 82
 83
 84def main() -> None:
 85    book = md.Spellbook(aetheric_frame="hotswap-world")
 86    v1_id = book.bind(
 87        spell=HotSwapPricingV1, existence="unique", permissions="create",
 88    )
 89    conduit = book.conjure(dynamic=True, name="hotswap-root")
 90    print("v1 bound and active")
 91
 92    # THE INDEX IS PUBLICLY REACHABLE from the active spell, and it is a
 93    # LINEAGE: a set of member ids plus exactly one selection.
 94    active = conduit.get_spell_by_spell_id(v1_id)
 95    assert active is not None
 96    index = active.spell_index
 97    assert index.selected_spell_id == v1_id
 98    assert index.spells_in_index() == {v1_id}
 99    print("index: 1 member, selected =", v1_id[:12], "...")
100
101    # v1 RESOLVES. This is the world as it stands.
102    before = conduit.meld("HotSwapPricingV1")
103    assert before.rate == 100
104    print("meld -> rate", before.rate)
105
106    # STAGE A CANDIDATE. It is bound, owned, and a real member of the
107    # lineage - and it changes nothing about what resolves.
108    v2_id = conduit.bind_inactive(
109        spell=HotSwapPricingV2,
110        spell_index=index,
111        existence="unique",
112        permissions="create",
113    )
114    assert index.spells_in_index() == {v1_id, v2_id}
115    assert index.selected_spell_id == v1_id, (
116        "staging must NOT move the selection - preparing a swap and "
117        "taking it are two different acts"
118    )
119    print()
120    print("v2 staged: 2 members, selection UNCHANGED")
121
122    # AND THE PARKED CANDIDATE IS INERT. The expensive half of the swap
123    # has already happened - v2 is compiled, registered and owned - while
124    # the old version is still the one serving traffic.
125    still = conduit.meld("HotSwapPricingV1")
126    assert still.rate == 100
127    print("meld -> rate", still.rate, " (v2 is parked, not resolvable)")
128    print("  the costly half of a swap is done while v1 still serves")
129
130    # A SPELL_ID NAMES ONE VERSION. Looking up the parked spell_id answers
131    # the parked version itself - not the member live for this lineage -
132    # and the notch below is given that same spell_id.
133    resolved_from_parked_id = conduit.get_spell_by_spell_id(v2_id)
134    assert resolved_from_parked_id is not None
135    assert resolved_from_parked_id.spell_id == v2_id
136    print()
137    print("looking up the PARKED spell_id returned the PARKED spell")
138    print("  and the notch is told the same version by the same spell_id")
139
140    # TAKE THE SWAP. The spell_id bind_inactive returned names the version;
141    # the notch resolves it inside the call and checks that it belongs to
142    # this index before anything moves.
143    promoted = conduit.notch_spell(spell_index=index, spell_id=v2_id)
144    assert promoted.spell_id == v2_id
145    assert index.selected_spell_id == v2_id
146    assert index.spells_in_index() == {v1_id, v2_id}, (
147        "a notch moves the selection; it removes no member"
148    )
149    after = conduit.meld(spell_id=v2_id)
150    assert after.rate == 250
151    print()
152    print("notch by spell_id: v2 selected, meld -> rate", after.rate)
153
154    # ROLL BACK: the same verb pointed the other way, because v1 was
155    # parked, not destroyed.
156    conduit.notch_spell(spell_index=index, spell_id=v1_id)
157    assert index.selected_spell_id == v1_id
158    back = conduit.meld(spell_id=v1_id)
159    assert back.rate == 100
160    print("notch back by spell_id: v1 selected, meld -> rate", back.rate)
161
162    print()
163    print("an index is a lineage with one selection")
164    print("staging adds a member; only a notch moves the selection")
165    print("nothing is destroyed by a notch, which is why rolling back")
166    print("is the same act and not a restore")
167
168
169if __name__ == "__main__":
170    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

API contracts๏ƒ