On this page
The gate that waits for readers๏
๐ต Expert ยท Lesson 35
THE ADMISSION GATE, and why it is not a mutex. Expert 16 rewired a running world and 17 staged a swap on a live object. Neither said what makes those safe. This does.
MELDER HAS THREE GATES AND THEY EXIST FOR ONE REASON: RiftGate admission to a Rift's guarded paths (here) CreationGate the conduit's meld path LoadGate crystallizer loads "All three exist because some operations must wait for READERS rather than for other writers." Read that twice. A mutex protects writers from each other. These make a writer wait until the last READER has left - which is the only way to swap something out from under live callers without tearing.
TICKETS ARE WHAT MAKE DRAINING TRUTHFUL. A caller registers a ticket entering guarded work and unregisters on exit, and drain waits for the count to hit zero. A boolean "busy" flag would be a guess; "a meld holds its ticket across the whole executor, so ticket-zero genuinely means no reader is inside".
TWO CONTROL MODES, AND CONFLATING THEM IS THE BUG. BLOCKING is REVERSIBLE. Disable parks new entrants, open() releases them. This is what an ACL-driven projection refresh needs: block entrants, drain, refresh once, reopen. TERMINAL CLOSE is ONE-WAY. It exists for shutdown, "where reopening would be wrong". One is a door you hold shut; the other is a door you brick up.
TWO ENTRY MODES, AND THIS ONE IS A POLICY CHOICE ABOUT YOUR AGENTS. While a gate is disabled, admit() either WAITS or RAISES: "wait" the caller blocks until someone reopens "raise" the caller is told no, immediately An agent that blocks is patient; an agent that raises can go do something else. Neither is right by default, which is why it is a setting rather than a behaviour.
AND NOW THE HONEST PART, WHICH IS THE REASON THIS LESSON EXISTS. NO SINGLE VERB PROVES A RIFT IS EMPTY, and melder says so in its own docstrings rather than letting you assume otherwise: disable_rift_gate stops NEW entry. It "does NOT wait for threads already inside". close_and_wait_rift drains - but the timeout BOUNDS the wait, so "a return does not by itself prove the rift is empty". count_active_rift_threads tells you the truth, and is "a DIAGNOSTIC, not a synchronization primitive - do not spin on it". So quiescence is: close-and-wait (bounded), THEN check the count. Most systems ship a drain() that implies a guarantee it cannot make. This one hands you three verbs and tells you what each one does not cover.
ONE TRAP, STATED PLAINLY: disable/enable SILENTLY NO-OP for an unknown rift id. A typo does not raise - it succeeds and changes nothing.
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/35_the_gate_that_waits_for_readers.py
py -3.14t UX_and_AIX_experiences/04_expert/35_the_gate_that_waits_for_readers.py
Public surface๏
Nexus.get_rift_gate / enable_rift_gate / disable_rift_gate / set_rift_gate_entry_mode / count_active_rift_threads / count_active_rift_threads_total / close_and_wait_rift, Rift.id, and a command refused at the closed gate
Code๏
1"""
2TIER: expert (35)
3GOAL: THE ADMISSION GATE, and why it is not a mutex. Expert 16 rewired a
4 running world and 17 staged a swap on a live object. Neither said
5 what makes those safe. This does.
6
7 MELDER HAS THREE GATES AND THEY EXIST FOR ONE REASON:
8 RiftGate admission to a Rift's guarded paths (here)
9 CreationGate the conduit's meld path
10 LoadGate crystallizer loads
11 "All three exist because some operations must wait for READERS
12 rather than for other writers." Read that twice. A mutex protects
13 writers from each other. These make a writer wait until the last
14 READER has left - which is the only way to swap something out from
15 under live callers without tearing.
16
17 TICKETS ARE WHAT MAKE DRAINING TRUTHFUL. A caller registers a ticket
18 entering guarded work and unregisters on exit, and drain waits for
19 the count to hit zero. A boolean "busy" flag would be a guess; "a
20 meld holds its ticket across the whole executor, so ticket-zero
21 genuinely means no reader is inside".
22
23 TWO CONTROL MODES, AND CONFLATING THEM IS THE BUG.
24 BLOCKING is REVERSIBLE. Disable parks new entrants, open() releases
25 them. This is what an ACL-driven projection refresh needs: block
26 entrants, drain, refresh once, reopen.
27 TERMINAL CLOSE is ONE-WAY. It exists for shutdown, "where
28 reopening would be wrong".
29 One is a door you hold shut; the other is a door you brick up.
30
31 TWO ENTRY MODES, AND THIS ONE IS A POLICY CHOICE ABOUT YOUR AGENTS.
32 While a gate is disabled, `admit()` either WAITS or RAISES:
33 "wait" the caller blocks until someone reopens
34 "raise" the caller is told no, immediately
35 An agent that blocks is patient; an agent that raises can go do
36 something else. Neither is right by default, which is why it is a
37 setting rather than a behaviour.
38
39 AND NOW THE HONEST PART, WHICH IS THE REASON THIS LESSON EXISTS.
40 NO SINGLE VERB PROVES A RIFT IS EMPTY, and melder says so in its own
41 docstrings rather than letting you assume otherwise:
42 disable_rift_gate stops NEW entry. It "does NOT wait for
43 threads already inside".
44 close_and_wait_rift drains - but the timeout BOUNDS the wait,
45 so "a return does not by itself prove the
46 rift is empty".
47 count_active_rift_threads
48 tells you the truth, and is "a DIAGNOSTIC,
49 not a synchronization primitive - do not
50 spin on it".
51 So quiescence is: close-and-wait (bounded), THEN check the count.
52 Most systems ship a `drain()` that implies a guarantee it cannot
53 make. This one hands you three verbs and tells you what each one
54 does not cover.
55
56 ONE TRAP, STATED PLAINLY: disable/enable SILENTLY NO-OP for an
57 unknown rift id. A typo does not raise - it succeeds and changes
58 nothing.
59SURFACE EXERCISED: Nexus.get_rift_gate / enable_rift_gate /
60 disable_rift_gate / set_rift_gate_entry_mode /
61 count_active_rift_threads /
62 count_active_rift_threads_total / close_and_wait_rift,
63 Rift.id, and a command refused at the closed gate
64VERIFY: authored 2026-08-05; not yet run.
65"""
66import melder as md
67
68
69FRAME = "gatekeep-world"
70
71SAFE = "result = 2 + 2\n"
72
73
74class Reader:
75 def __init__(self) -> None:
76 self.name = "reader"
77
78
79def main() -> None:
80 spellbook_configuration = (
81 md.SpellbookConfiguration(FRAME).with_defaults().finalize()
82 )
83 book = md.Spellbook(aetheric_frame=FRAME,
84 configuration=spellbook_configuration)
85 book.configure_aether_frame(
86 system_state="dynamic",
87 disposal=None,
88 disposal_method_names=None,
89 rift_enabled=True,
90 ai_native=True,
91 )
92 book.bind(spell=Reader, existence="unique", permissions="create",
93 binding_name="gatekeep-reader")
94 book.conjure(name="gatekeep-root")
95
96 nexus = md.Nexus()
97 system_configuration = nexus.create_configuration()
98 system_configuration.with_rift_creation_enabled(True)
99 system_configuration.with_allowed_target_frame_names([FRAME])
100 nexus.activate(system_configuration)
101 rift_configuration = nexus.create_rift_configuration()
102 rift_configuration.with_space_type("codegen")
103 rift = nexus.create_rift(configuration=rift_configuration,
104 rift_name="gatekeeper")
105 rift.mark_active()
106 rift.create_frame_link(FRAME)
107 commands = rift.space.command_system
108
109 rift_id = rift.id
110
111 # `get_rift_gate` is used here the way `enable_rift_gate`'s own
112 # contract prescribes - as an EXISTENCE CHECK, because the enable and
113 # disable verbs silently no-op on an unknown id: "a typo'd id looks
114 # like success. Confirm with get_rift_gate(...) when the id is not
115 # known-good." The gate object itself is kernel machinery and this
116 # lesson never drives it; every state change below goes through Nexus.
117 assert nexus.get_rift_gate(rift_id) is not None, "this rift has a gate"
118 assert nexus.get_rift_gate("no-such-rift-id") is None, (
119 "an unknown id must answer None rather than raising"
120 )
121 print("rift:", rift_id[:14], "... gate registered: True")
122 print(" (None for an unknown id is an ANSWER, not an error channel)")
123
124 # NOBODY IS INSIDE. The count is a diagnostic, and right now it is a
125 # true one because this thread is not in a guarded call.
126 assert nexus.count_active_rift_threads(rift_id) == 0
127 assert nexus.count_active_rift_threads_total() == 0
128 print("active tickets:", nexus.count_active_rift_threads(rift_id),
129 "| across all rifts:", nexus.count_active_rift_threads_total())
130
131 # THE ENTRY MODE IS A POLICY CHOICE. Set it to `raise` BEFORE closing
132 # the gate - on a single thread, `wait` mode plus a closed gate is a
133 # deadlock, and that is not a melder bug, it is what "wait" means.
134 nexus.set_rift_gate_entry_mode(rift_id, "raise")
135 print()
136 print("entry mode set to 'raise' - a disabled gate will refuse rather")
137 print("than park the caller. On one thread, 'wait' here would hang")
138 print("forever, and correctly so.")
139
140 # CLOSE THE DOOR. New entry stops immediately.
141 nexus.disable_rift_gate(rift_id)
142 try:
143 commands.validate_codegen(SAFE, frame_name=FRAME)
144 raise AssertionError("expected the gate to refuse admission")
145 except RuntimeError as refused:
146 print()
147 print("validate_codegen at a disabled gate ->", str(refused)[:70])
148 print(" the command never ran. Admission is checked BEFORE the")
149 print(" work, not inside it")
150
151 # AND THE SAME COMMAND WORKS THE MOMENT IT REOPENS. Reversible.
152 nexus.enable_rift_gate(rift_id)
153 verdict = commands.validate_codegen(SAFE, frame_name=FRAME)
154 assert verdict["accepted"] is True
155 print()
156 print("enable_rift_gate -> the same call now answers:",
157 verdict["accepted"])
158 print(" blocking mode is REVERSIBLE. That is the whole point: block")
159 print(" entrants, drain, refresh the projection once, reopen.")
160
161 # THE SILENT NO-OP. A typo'd rift id does not raise.
162 nexus.disable_rift_gate("no-such-rift-id")
163 still_open = commands.validate_codegen(SAFE, frame_name=FRAME)
164 assert still_open["accepted"] is True, (
165 "disabling an unknown rift must not affect a real one"
166 )
167 print()
168 print("disable_rift_gate('no-such-rift-id') -> silently did nothing,")
169 print(" and our real gate is still open. A typo here does not raise;")
170 print(" it succeeds and changes nothing, which is why the contract")
171 print(" tells you to confirm the id with get_rift_gate(...) first -")
172 print(" the check at the top of this lesson is that idiom, not a")
173 print(" reach into the gate object")
174
175 # QUIESCENCE IS TWO STEPS, NOT ONE.
176 nexus.close_and_wait_rift(rift_id, timeout=5.0, interval=0.05)
177 remaining = nexus.count_active_rift_threads(rift_id)
178 assert remaining == 0, remaining
179 print()
180 print("close_and_wait_rift returned, and THEN we checked:", remaining,
181 "tickets")
182 print(" the return alone does not prove empty - the timeout bounds")
183 print(" the wait. Melder documents that rather than implying a")
184 print(" guarantee it cannot make, which is why quiescence is")
185 print(" close-and-wait FOLLOWED BY a count check")
186
187 # TERMINAL CLOSE IS ONE-WAY. The gate does not reopen for new work.
188 try:
189 commands.validate_codegen(SAFE, frame_name=FRAME)
190 raise AssertionError("expected the terminally closed gate to refuse")
191 except RuntimeError as closed:
192 print()
193 print("after close_and_wait ->", str(closed)[:70])
194 print(" terminal close is ONE-WAY. Blocking mode is a door you")
195 print(" hold shut; this is a door you brick up, and it exists for")
196 print(" shutdown where reopening would be wrong")
197
198 print()
199 print("a mutex protects writers from each other")
200 print("a gate makes a writer wait for the last READER to leave")
201 print("that is what lets melder swap a live object without tearing")
202
203
204if __name__ == "__main__":
205 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.