personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3from __future__ import annotations
4
5import ast
6
7import pytest
8
9import scripts.build_native_sol_journal_host_commands as journal_host
10
11SERVICE_COMMANDS = ("setup", "think") + tuple(f"svc{i:02d}" for i in range(41))
12UNIVERSAL_COMMANDS = ("check", "contract", "doctor", "link")
13SERVICE_ALIASES = ("down", "up")
14
15
16def _raw_source(lines: list[str]) -> str:
17 return "\n".join([*lines, ""])
18
19
20def _source(commands: dict[str, str], aliases: dict[str, str]) -> str:
21 command_lines = [
22 f' "{name}": Command("module.{name.replace("-", "_")}", "{surface}"),'
23 for name, surface in sorted(commands.items())
24 ]
25 alias_lines = [
26 f' "{name}": Alias("module.{name.replace("-", "_")}", [], "{surface}"),'
27 for name, surface in sorted(aliases.items())
28 ]
29 return _raw_source(
30 [
31 "COMMANDS = {",
32 *command_lines,
33 "}",
34 "",
35 "ALIASES = {",
36 *alias_lines,
37 "}",
38 ]
39 )
40
41
42def _base_commands() -> dict[str, str]:
43 return {
44 **{name: "service" for name in SERVICE_COMMANDS},
45 **{name: "universal" for name in UNIVERSAL_COMMANDS},
46 }
47
48
49def _base_aliases() -> dict[str, str]:
50 return {name: "service" for name in SERVICE_ALIASES}
51
52
53def _error(commands: dict[str, str], aliases: dict[str, str]) -> str:
54 return _raw_error(_source(commands, aliases))
55
56
57def _raw_error(source: str) -> str:
58 with pytest.raises(RuntimeError) as error:
59 journal_host.extract_partitions(source)
60 message = str(error.value)
61 assert "\n" not in message
62 return message
63
64
65def _old_combined_service_surface_count(
66 commands: dict[str, str], aliases: dict[str, str]
67) -> int:
68 service_commands = {
69 name for name, surface in commands.items() if surface == "service"
70 }
71 service_aliases = {
72 name for name, surface in aliases.items() if surface == "service"
73 }
74 return len(service_commands | service_aliases)
75
76
77def _raw_service_command_literal_count(source: str) -> int:
78 tree = ast.parse(source)
79 count = 0
80 for registry_literal in journal_host.scan_registry_literals(tree):
81 if registry_literal.name != "COMMANDS":
82 continue
83 for key, value in zip(
84 registry_literal.node.keys, registry_literal.node.values, strict=True
85 ):
86 key_value = journal_host.literal_key(key)
87 if (
88 key_value is not None
89 and journal_host.call_surface(value, registry_literal.surface_position)
90 == "service"
91 ):
92 count += 1
93 return count
94
95
96def test_extract_keeps_service_commands_and_aliases_as_sorted_moved_list() -> None:
97 moved = journal_host.extract(_source(_base_commands(), _base_aliases()))
98
99 assert len(moved) == 45
100 assert moved == sorted((*SERVICE_COMMANDS, *SERVICE_ALIASES))
101
102
103def test_duplicate_commands_rejected_before_partition_validation() -> None:
104 source = _raw_source(
105 [
106 "COMMANDS = {",
107 ' "ghost": Command("module.ghost", SURFACE),',
108 ' "ghost": Command("module.ghost_again", SURFACE),',
109 " **EXTRA_COMMANDS,",
110 ' NAME: Command("module.dynamic", "service"),',
111 "}",
112 "",
113 "ALIASES = {",
114 "}",
115 ]
116 )
117
118 message = _raw_error(source)
119
120 assert (
121 message == "journal-host duplicate registry keys: COMMANDS 'ghost' "
122 "[line 2 surface=<unavailable>, line 3 surface=<unavailable>]"
123 )
124
125
126def test_duplicate_service_command_preserves_old_count_but_is_rejected() -> None:
127 source = _raw_source(
128 [
129 "COMMANDS = {",
130 ' "think": Command("module.think", "service"),',
131 ' "setup": Command("module.setup", "service"),',
132 ' "think": Command("module.think_again", "service"),',
133 ' "svc00": Command("module.svc00", "service"),',
134 ' "svc01": Command("module.svc01", "service"),',
135 ' "svc02": Command("module.svc02", "service"),',
136 ' "svc03": Command("module.svc03", "service"),',
137 ' "svc04": Command("module.svc04", "service"),',
138 ' "svc05": Command("module.svc05", "service"),',
139 ' "svc06": Command("module.svc06", "service"),',
140 ' "svc07": Command("module.svc07", "service"),',
141 ' "svc08": Command("module.svc08", "service"),',
142 ' "svc09": Command("module.svc09", "service"),',
143 ' "svc10": Command("module.svc10", "service"),',
144 ' "svc11": Command("module.svc11", "service"),',
145 ' "svc12": Command("module.svc12", "service"),',
146 ' "svc13": Command("module.svc13", "service"),',
147 ' "svc14": Command("module.svc14", "service"),',
148 ' "svc15": Command("module.svc15", "service"),',
149 ' "svc16": Command("module.svc16", "service"),',
150 ' "svc17": Command("module.svc17", "service"),',
151 ' "svc18": Command("module.svc18", "service"),',
152 ' "svc19": Command("module.svc19", "service"),',
153 ' "svc20": Command("module.svc20", "service"),',
154 ' "svc21": Command("module.svc21", "service"),',
155 ' "svc22": Command("module.svc22", "service"),',
156 ' "svc23": Command("module.svc23", "service"),',
157 ' "svc24": Command("module.svc24", "service"),',
158 ' "svc25": Command("module.svc25", "service"),',
159 ' "svc26": Command("module.svc26", "service"),',
160 ' "svc27": Command("module.svc27", "service"),',
161 ' "svc28": Command("module.svc28", "service"),',
162 ' "svc29": Command("module.svc29", "service"),',
163 ' "svc30": Command("module.svc30", "service"),',
164 ' "svc31": Command("module.svc31", "service"),',
165 ' "svc32": Command("module.svc32", "service"),',
166 ' "svc33": Command("module.svc33", "service"),',
167 ' "svc34": Command("module.svc34", "service"),',
168 ' "svc35": Command("module.svc35", "service"),',
169 ' "svc36": Command("module.svc36", "service"),',
170 ' "svc37": Command("module.svc37", "service"),',
171 ' "svc38": Command("module.svc38", "service"),',
172 ' "svc39": Command("module.svc39", "service"),',
173 ' "check": Command("module.check", "universal"),',
174 ' "contract": Command("module.contract", "universal"),',
175 ' "doctor": Command("module.doctor", "universal"),',
176 ' "link": Command("module.link", "universal"),',
177 "}",
178 "",
179 "ALIASES = {",
180 ' "down": Alias("module.down", [], "service"),',
181 ' "up": Alias("module.up", [], "service"),',
182 "}",
183 ]
184 )
185
186 assert _raw_service_command_literal_count(source) == 43
187 message = _raw_error(source)
188
189 assert (
190 message == "journal-host duplicate registry keys: COMMANDS 'think' "
191 "[line 2 surface=service, line 4 surface=service]"
192 )
193
194
195def test_duplicate_universal_command_is_rejected() -> None:
196 source = _raw_source(
197 [
198 "COMMANDS = {",
199 ' "doctor": Command("module.doctor", "universal"),',
200 ' "doctor": Command("module.doctor_again", "universal"),',
201 "}",
202 "",
203 "ALIASES = {",
204 "}",
205 ]
206 )
207
208 message = _raw_error(source)
209
210 assert (
211 message == "journal-host duplicate registry keys: COMMANDS 'doctor' "
212 "[line 2 surface=universal, line 3 surface=universal]"
213 )
214
215
216def test_duplicate_service_alias_is_rejected() -> None:
217 source = _raw_source(
218 [
219 "COMMANDS = {",
220 "}",
221 "",
222 "ALIASES = {",
223 ' "up": Alias("module.up", [], "service"),',
224 ' "up": Alias("module.up_again", [], "service"),',
225 "}",
226 ]
227 )
228
229 message = _raw_error(source)
230
231 assert (
232 message == "journal-host duplicate registry keys: ALIASES 'up' "
233 "[line 5 surface=service, line 6 surface=service]"
234 )
235
236
237def test_duplicate_keys_sort_key_names_before_source_order() -> None:
238 source = _raw_source(
239 [
240 "COMMANDS = {",
241 ' "zeta": Command("module.zeta", "service"),',
242 ' "zeta": Command("module.zeta_again", "service"),',
243 ' "alpha": Command("module.alpha", "service"),',
244 ' "alpha": Command("module.alpha_again", "service"),',
245 "}",
246 "",
247 "ALIASES = {",
248 "}",
249 ]
250 )
251
252 message = _raw_error(source)
253
254 assert (
255 message == "journal-host duplicate registry keys: COMMANDS 'alpha' "
256 "[line 4 surface=service, line 5 surface=service]; COMMANDS 'zeta' "
257 "[line 2 surface=service, line 3 surface=service]"
258 )
259
260
261def test_duplicate_command_reports_disagreeing_surfaces() -> None:
262 source = _raw_source(
263 [
264 "COMMANDS = {",
265 ' "think": Command("module.think", "service"),',
266 ' "think": Command("module.think_again", "universal"),',
267 "}",
268 "",
269 "ALIASES = {",
270 "}",
271 ]
272 )
273
274 message = _raw_error(source)
275
276 assert (
277 message == "journal-host duplicate registry keys: COMMANDS 'think' "
278 "[line 2 surface=service, line 3 surface=universal]"
279 )
280
281
282def test_duplicates_aggregate_across_plain_and_annotated_assignments() -> None:
283 source = _raw_source(
284 [
285 "COMMANDS = {",
286 ' "think": Command("module.think", "service"),',
287 "}",
288 "COMMANDS: dict[str, Command] = {",
289 ' "think": Command("module.think_again", "service"),',
290 "}",
291 "ALIASES: dict[str, Alias] = {",
292 ' "up": Alias("module.up", [], "service"),',
293 "}",
294 "ALIASES = {",
295 ' "up": Alias("module.up_again", [], "service"),',
296 "}",
297 ]
298 )
299
300 message = _raw_error(source)
301
302 assert (
303 message == "journal-host duplicate registry keys: COMMANDS 'think' "
304 "[line 2 surface=service, line 5 surface=service]; ALIASES 'up' "
305 "[line 8 surface=service, line 11 surface=service]"
306 )
307
308
309def test_simultaneous_duplicates_report_unavailable_surface() -> None:
310 source = _raw_source(
311 [
312 "COMMANDS = {",
313 ' "think": Command("module.think", "service"),',
314 ' "think": Command("module.think_again", SURFACE),',
315 "}",
316 "ALIASES = {",
317 ' "up": Alias("module.up", [], "service"),',
318 ' "up": Alias("module.up_again", [], alias_surface()),',
319 " **EXTRA_ALIASES,",
320 ' ALIAS_NAME: Alias("module.dynamic", [], "service"),',
321 "}",
322 ]
323 )
324
325 message = _raw_error(source)
326
327 assert (
328 message == "journal-host duplicate registry keys: COMMANDS 'think' "
329 "[line 2 surface=service, line 3 surface=<unavailable>]; ALIASES 'up' "
330 "[line 6 surface=service, line 7 surface=<unavailable>]"
331 )
332
333
334def test_spread_and_non_literal_keys_are_skipped_on_a_valid_source() -> None:
335 source = _raw_source(
336 [
337 "COMMANDS = {",
338 *[
339 f' "{name}": Command("module.{name.replace("-", "_")}", "service"),'
340 for name in SERVICE_COMMANDS
341 ],
342 " **EXTRA_COMMANDS,",
343 ' NAME: Command("module.dynamic", "service"),',
344 *[
345 f' "{name}": Command("module.{name.replace("-", "_")}", "universal"),'
346 for name in UNIVERSAL_COMMANDS
347 ],
348 "}",
349 "",
350 "ALIASES = {",
351 *[
352 f' "{name}": Alias("module.{name.replace("-", "_")}", [], "service"),'
353 for name in SERVICE_ALIASES
354 ],
355 " **EXTRA_ALIASES,",
356 ' ALIAS_NAME: Alias("module.dynamic", [], "service"),',
357 "}",
358 ]
359 )
360
361 moved = journal_host.extract(source)
362
363 assert len(moved) == 45
364 assert moved == sorted((*SERVICE_COMMANDS, *SERVICE_ALIASES))
365
366
367def test_production_registry_extracts_expected_partitions() -> None:
368 partitions = journal_host.extract_partitions()
369
370 assert len(partitions.service_commands) == 43
371 assert set(partitions.service_aliases) == {"up", "down"}
372 assert set(partitions.universal_commands) == {
373 "doctor",
374 "check",
375 "contract",
376 "link",
377 }
378 assert partitions.universal_aliases == ()
379 assert len(journal_host.extract()) == 45
380
381
382def test_service_command_count_rejects_44_service_commands_and_1_alias() -> None:
383 commands = _base_commands()
384 commands["audio"] = "service"
385 aliases = _base_aliases()
386 del aliases["down"]
387
388 assert _old_combined_service_surface_count(commands, aliases) == 45
389 message = _error(commands, aliases)
390
391 assert message.startswith("journal-host service COMMANDS count 44 != 43: ")
392 assert "'audio'" in message
393
394
395def test_service_command_count_rejects_42_service_commands_and_3_aliases() -> None:
396 commands = _base_commands()
397 del commands["svc00"]
398 aliases = _base_aliases()
399 aliases["extra"] = "service"
400
401 assert _old_combined_service_surface_count(commands, aliases) == 45
402 message = _error(commands, aliases)
403
404 assert message.startswith("journal-host service COMMANDS count 42 != 43: ")
405
406
407@pytest.mark.parametrize("alias", ["up", "down"])
408def test_service_aliases_reject_missing_expected_alias(alias: str) -> None:
409 aliases = _base_aliases()
410 del aliases[alias]
411
412 message = _error(_base_commands(), aliases)
413
414 assert (
415 message
416 == f"journal-host service ALIASES drifted; missing=['{alias}']; extra=[]; changed_surface=[]"
417 )
418
419
420@pytest.mark.parametrize("alias", ["up", "down"])
421def test_service_aliases_report_reclassified_alias(alias: str) -> None:
422 aliases = _base_aliases()
423 aliases[alias] = "universal"
424
425 message = _error(_base_commands(), aliases)
426
427 assert (
428 message
429 == f"journal-host service ALIASES drifted; missing=['{alias}']; extra=[]; changed_surface=['{alias}']"
430 )
431
432
433@pytest.mark.parametrize("command", ["doctor", "check", "contract", "link"])
434def test_universal_commands_reject_missing_expected_command(command: str) -> None:
435 commands = _base_commands()
436 del commands[command]
437
438 message = _error(commands, _base_aliases())
439
440 assert (
441 message
442 == f"journal-host universal COMMANDS drifted; missing=['{command}']; extra=[]; changed_surface=[]"
443 )
444
445
446def test_universal_commands_report_reclassified_command() -> None:
447 commands = _base_commands()
448 commands["doctor"] = "service"
449 del commands["svc00"]
450
451 message = _error(commands, _base_aliases())
452
453 assert (
454 message
455 == "journal-host universal COMMANDS drifted; missing=['doctor']; extra=[]; changed_surface=['doctor']"
456 )
457
458
459def test_commands_and_aliases_overlap_is_rejected() -> None:
460 aliases = _base_aliases()
461 aliases["svc00"] = "service"
462
463 message = _error(_base_commands(), aliases)
464
465 assert message == "journal-host COMMANDS and ALIASES overlap: ['svc00']"
466
467
468def test_empty_extraction_is_rejected() -> None:
469 message = _error({}, {})
470
471 assert message == "journal-host command extraction is empty"
472
473
474@pytest.mark.parametrize("sentinel", ["think", "setup"])
475def test_service_command_sentinel_is_required(sentinel: str) -> None:
476 commands = _base_commands()
477 del commands[sentinel]
478 commands["aaa"] = "service"
479
480 message = _error(commands, _base_aliases())
481
482 assert message == f"journal-host service COMMANDS missing sentinels: ['{sentinel}']"