From f68cb8d68231f5318e53b6836fd1a750543bf9c7 Mon Sep 17 00:00:00 2001 From: Igor Lins e Silva <4753812+igorls@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:22:06 -0300 Subject: [PATCH 1/2] fix: scope derived graph state to explicit palace --- mempalace/config.py | 12 +++++++++++- mempalace/convo_miner.py | 8 ++++---- mempalace/format_miner.py | 4 ++-- mempalace/hallways.py | 13 +++++++++---- mempalace/miner.py | 23 ++++++++++++++--------- mempalace/palace_graph.py | 12 +++++++++++- tests/test_config.py | 15 +++++++++++++++ tests/test_format_miner.py | 9 +++++++-- tests/test_hallways.py | 18 ++++++++++++++++++ tests/test_miner.py | 14 ++++++++------ tests/test_palace_graph_tunnels.py | 15 +++++++++++++++ 11 files changed, 114 insertions(+), 29 deletions(-) diff --git a/mempalace/config.py b/mempalace/config.py index 7f0a2e3..243ccc2 100644 --- a/mempalace/config.py +++ b/mempalace/config.py @@ -342,18 +342,26 @@ class MempalaceConfig: Load order: env vars > config file > defaults. """ - def __init__(self, config_dir=None): + def __init__(self, config_dir=None, palace_path=None): """Initialize config. Args: config_dir: Override config directory (useful for testing). Defaults to ~/.mempalace. + palace_path: Explicit palace data directory. This is primarily + used by CLI operations that received ``--palace``; + it takes precedence over environment and file config. """ self._config_dir = ( Path(config_dir) if config_dir else Path(os.path.expanduser("~/.mempalace")) ) self._config_file = self._config_dir / "config.json" self._people_map_file = self._config_dir / "people_map.json" + self._palace_path_override = ( + os.path.abspath(os.path.expanduser(str(palace_path))) + if palace_path is not None + else None + ) self._file_config = {} if self._config_file.exists(): @@ -366,6 +374,8 @@ class MempalaceConfig: @property def palace_path(self): """Path to the memory palace data directory.""" + if self._palace_path_override is not None: + return self._palace_path_override env_val = os.environ.get("MEMPALACE_PALACE_PATH") or os.environ.get("MEMPAL_PALACE_PATH") if env_val: # Normalize: expand ~ and collapse .. to match the CLI --palace diff --git a/mempalace/convo_miner.py b/mempalace/convo_miner.py index 26dcbbc..0ea784b 100644 --- a/mempalace/convo_miner.py +++ b/mempalace/convo_miner.py @@ -705,7 +705,7 @@ def mine_convos( ) -def _compute_hallways_for_wing_safe(wing, collection, drawers_filed): +def _compute_hallways_for_wing_safe(wing, collection, drawers_filed, config=None): """Auto-populate the associative graph from the entities just mined. Best-effort: hallway computation must never fail an otherwise-good mine, and is @@ -716,7 +716,7 @@ def _compute_hallways_for_wing_safe(wing, collection, drawers_filed): try: from .hallways import compute_hallways_for_wing - compute_hallways_for_wing(wing, col=collection) + compute_hallways_for_wing(wing, col=collection, config=config) except Exception as exc: print(f" (hallways skipped: {exc})") @@ -732,7 +732,7 @@ def _mine_convos_impl( ): from .config import MempalaceConfig - palace_config = MempalaceConfig() + palace_config = MempalaceConfig(palace_path=palace_path) cfg_chunk_size = palace_config.chunk_size # Only override convo_miner's MIN_CHUNK_SIZE when the user has set # min_chunk_size explicitly. min_chunk_size_explicit returns the @@ -889,7 +889,7 @@ def _mine_convos_impl( # Compute hallways before the FTS5 validation: the latter opens a direct sqlite # connection to the Chroma DB, which can invalidate the live collection handle on # some Chroma builds and make the hallway fetch fail. - _compute_hallways_for_wing_safe(wing, collection, total_drawers) + _compute_hallways_for_wing_safe(wing, collection, total_drawers, config=palace_config) _validate_palace_fts5_after_mine(palace_path) print(f"\n{'=' * 55}") diff --git a/mempalace/format_miner.py b/mempalace/format_miner.py index 296ca29..a7800cc 100644 --- a/mempalace/format_miner.py +++ b/mempalace/format_miner.py @@ -750,7 +750,7 @@ def mine_formats( # min_chunk_size) are now threaded through chunk_text below, so users # who customized their config see the effect in format-mode mining. # Per PR #1555 review (Gemini #3). - palace_config = MempalaceConfig() + palace_config = MempalaceConfig(palace_path=palace_path) format_path = Path(format_dir).expanduser().resolve() if not wing: @@ -945,7 +945,7 @@ def mine_formats( # skipped quietly. if not dry_run: try: - tunnels_added = _compute_topic_tunnels_for_wing(wing) + tunnels_added = _compute_topic_tunnels_for_wing(wing, config=palace_config) if tunnels_added: print(f"\n Topic tunnels: +{tunnels_added} cross-wing link(s)") except Exception as exc: diff --git a/mempalace/hallways.py b/mempalace/hallways.py index 971bd86..a4d3afa 100644 --- a/mempalace/hallways.py +++ b/mempalace/hallways.py @@ -199,6 +199,7 @@ def compute_hallways_for_wing( wing: str, col=None, min_count: int = 2, + config=None, ) -> list[dict]: """Compute entity-pair hallways for one wing. @@ -229,6 +230,10 @@ def compute_hallways_for_wing( hallway between two entities. Default 2 — single co-occurrences are noise (entities mentioned together once in one drawer); two or more is a real signal. Clamped to ``>=1``. + config: Optional ``MempalaceConfig`` selecting the palace-scoped + hallway sidecar. Callers using an explicit palace path must pass + the matching config so derived graph state cannot leak into the + default palace. Returns: List of hallway dicts created for this wing. Records for other @@ -310,7 +315,7 @@ def compute_hallways_for_wing( # across recomputes. Without this preservation, every mine wipes # the connection weights accumulated through use — defeating the # living-connection layer entirely. - existing = _load_hallways() + existing = _load_hallways(config) existing_dynamics_lookup: dict = {} for h in existing: if h.get("wing") != wing: @@ -360,7 +365,7 @@ def compute_hallways_for_wing( # 4. Persist — preserve other-wing records, replace this wing's records. preserved_other_wings = [h for h in existing if h.get("wing") != wing] - _save_hallways(preserved_other_wings + created) + _save_hallways(preserved_other_wings + created, config) return created @@ -370,9 +375,9 @@ def compute_hallways_for_wing( # ───────────────────────────────────────────────────────────────────────────── -def list_hallways(wing: Optional[str] = None) -> list[dict]: +def list_hallways(wing: Optional[str] = None, config=None) -> list[dict]: """List hallway records. Filter by ``wing`` if specified.""" - all_hallways = _load_hallways() + all_hallways = _load_hallways(config) if wing is None: return list(all_hallways) return [h for h in all_hallways if h.get("wing") == wing] diff --git a/mempalace/miner.py b/mempalace/miner.py index a84d51a..3b286e5 100644 --- a/mempalace/miner.py +++ b/mempalace/miner.py @@ -1873,12 +1873,15 @@ def _mine_impl( break if not dry_run: + from .config import MempalaceConfig + + graph_config = MempalaceConfig(palace_path=palace_path) # Cross-wing topic tunnels: after every file in this wing has been # processed, link this wing to any other wing that shares a # confirmed TOPIC label. Out of scope for v1: manifest-dependency # overlap, per-topic allow/deny lists, search-result surfacing. try: - tunnels_added = _compute_topic_tunnels_for_wing(wing) + tunnels_added = _compute_topic_tunnels_for_wing(wing, config=graph_config) if tunnels_added: print(f"\n Topic tunnels: +{tunnels_added} cross-wing link(s)") except Exception as e: @@ -1894,7 +1897,9 @@ def _mine_impl( # must never fail a mine; it's a derived analytic, not load-bearing # for the drawer write that already committed above. try: - hallways_created = compute_hallways_for_wing(wing, col=collection) + hallways_created = compute_hallways_for_wing( + wing, col=collection, config=graph_config + ) if hallways_created: print(f"\n Hallways: +{len(hallways_created)} within-wing entity link(s)") except Exception as e: @@ -1910,7 +1915,7 @@ def _mine_impl( # ``kind="entity"`` / ``kind="topic"``. Same fault-tolerance # pattern: never fail a mine over a derived analytic. try: - entity_tunnels_added = _compute_entity_tunnels_for_wing(wing) + entity_tunnels_added = _compute_entity_tunnels_for_wing(wing, config=graph_config) if entity_tunnels_added: print(f"\n Entity tunnels: +{entity_tunnels_added} cross-wing entity link(s)") except Exception as e: @@ -2034,7 +2039,7 @@ def _cleanup_mine_pid_file() -> None: pass -def _compute_topic_tunnels_for_wing(wing: str) -> int: +def _compute_topic_tunnels_for_wing(wing: str, config=None) -> int: """Drop tunnels between ``wing`` and every other wing that shares confirmed topics, honoring the ``topic_tunnel_min_count`` config knob. @@ -2047,13 +2052,13 @@ def _compute_topic_tunnels_for_wing(wing: str) -> int: topics_map = get_topics_by_wing() if not topics_map or wing not in topics_map: return 0 - cfg = MempalaceConfig() + cfg = config or MempalaceConfig() min_count = cfg.topic_tunnel_min_count - created = topic_tunnels_for_wing(wing, topics_map, min_count=min_count) + created = topic_tunnels_for_wing(wing, topics_map, min_count=min_count, config=cfg) return len(created) -def _compute_entity_tunnels_for_wing(wing: str) -> int: +def _compute_entity_tunnels_for_wing(wing: str, config=None) -> int: """Drop tunnels between ``wing`` and every other wing that shares an entity via the within-wing hallway primitive. @@ -2071,10 +2076,10 @@ def _compute_entity_tunnels_for_wing(wing: str) -> int: from .hallways import list_hallways from .palace_graph import entity_tunnels_for_wing - hallways = list_hallways() + hallways = list_hallways(config=config) if not hallways: return 0 - created = entity_tunnels_for_wing(wing, hallways) + created = entity_tunnels_for_wing(wing, hallways, config=config) return len(created) diff --git a/mempalace/palace_graph.py b/mempalace/palace_graph.py index 3011882..7119217 100644 --- a/mempalace/palace_graph.py +++ b/mempalace/palace_graph.py @@ -494,6 +494,7 @@ def create_tunnel( source_drawer_id: str = None, target_drawer_id: str = None, kind: str = "explicit", + config=None, ): """Create an explicit (symmetric) tunnel between two locations in the palace. @@ -520,6 +521,9 @@ def create_tunnel( topical link where rooms are synthetic ``topic:`` identifiers). Preserved on the stored dict so readers can distinguish real-room traversals from topic connections. + config: Optional ``MempalaceConfig`` selecting the palace and its + tunnel sidecar. Explicit-path callers must pass the matching + config instead of falling back to the ambient default palace. Returns: The stored tunnel dict. @@ -545,7 +549,7 @@ def create_tunnel( # mempalace.yaml from disk; before this change the helpers each # instantiated their own, triggering several redundant disk reads per # create_tunnel call (flagged by gemini-code-assist on #1469). - config = MempalaceConfig() + config = config or MempalaceConfig() # Validate room existence for explicit tunnels only. Use the verbatim wing # slugs here so #1504's hyphen-preserving write path remains intact. @@ -742,6 +746,7 @@ def compute_topic_tunnels( topics_by_wing: dict, min_count: int = 1, label_prefix: str = "shared topic", + config=None, ) -> list[dict]: """Create tunnels for every pair of wings that share >= ``min_count`` topics. @@ -817,6 +822,7 @@ def compute_topic_tunnels( target_room=room, label=f"{label_prefix}: {topic_name}", kind="topic", + config=config, ) created.append(tunnel) return created @@ -827,6 +833,7 @@ def topic_tunnels_for_wing( topics_by_wing: dict, min_count: int = 1, label_prefix: str = "shared topic", + config=None, ) -> list[dict]: """Compute topic tunnels involving a single wing. @@ -871,6 +878,7 @@ def topic_tunnels_for_wing( slice_map, min_count=min_count, label_prefix=label_prefix, + config=config, ) ) return created @@ -880,6 +888,7 @@ def entity_tunnels_for_wing( wing: str, hallways: list, label_prefix: str = "shared entity", + config=None, ) -> list: """Compute entity tunnels involving a single wing. @@ -946,6 +955,7 @@ def entity_tunnels_for_wing( target_room=room, label=f"{label_prefix}: {entity}", kind="entity", + config=config, ) created.append(tunnel) return created diff --git a/tests/test_config.py b/tests/test_config.py index 10ae74d..dc64867 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -888,3 +888,18 @@ def test_max_backups_bad_env_falls_back_to_config(monkeypatch, tmp_path): monkeypatch.setenv("MEMPALACE_MAX_BACKUPS", "garbage") cfg = MempalaceConfig(config_dir=str(tmp_path)) assert cfg.max_backups == 4 + + +def test_explicit_palace_path_overrides_env_and_file_config(monkeypatch, tmp_path): + configured = tmp_path / "configured" / "palace" + explicit = tmp_path / "explicit" / "../explicit" / "palace" + with open(tmp_path / "config.json", "w") as f: + json.dump({"palace_path": str(configured)}, f) + monkeypatch.setenv("MEMPALACE_PALACE_PATH", str(tmp_path / "environment" / "palace")) + + cfg = MempalaceConfig(config_dir=str(tmp_path), palace_path=str(explicit)) + + expected = os.path.abspath(os.path.expanduser(str(explicit))) + assert cfg.palace_path == expected + assert cfg.hallway_file == os.path.join(os.path.dirname(expected), "hallways.json") + assert cfg.tunnel_file == os.path.join(os.path.dirname(expected), "tunnels.json") diff --git a/tests/test_format_miner.py b/tests/test_format_miner.py index 5f998d0..b8f1c92 100644 --- a/tests/test_format_miner.py +++ b/tests/test_format_miner.py @@ -1158,7 +1158,9 @@ def test_mine_formats_calls_compute_topic_tunnels_after_loop(_mine_formats_mocks patch("mempalace.format_miner._compute_topic_tunnels_for_wing", return_value=0) as p_tun, ): mine_formats(format_dir=str(tmp), palace_path=str(tmp / "palace"), wing="wing_aya") - p_tun.assert_called_once_with("wing_aya") + p_tun.assert_called_once() + assert p_tun.call_args.args == ("wing_aya",) + assert p_tun.call_args.kwargs["config"].palace_path == str(tmp / "palace") def test_mine_formats_tunnel_failure_does_not_crash_mine(_mine_formats_mocks): @@ -1518,13 +1520,16 @@ def test_mine_formats_threads_chunk_size_from_user_config(monkeypatch, tmp_path: # Inject custom config values via a fake MempalaceConfig. class _FakeMempalaceConfig: + def __init__(self, **kwargs): + self._palace_path = kwargs.get("palace_path", str(tmp_path / "palace")) + chunk_size = 1234 chunk_overlap = 56 min_chunk_size = 78 @property def palace_path(self): - return str(tmp_path / "palace") + return self._palace_path monkeypatch.setattr(format_miner, "MempalaceConfig", _FakeMempalaceConfig) diff --git a/tests/test_hallways.py b/tests/test_hallways.py index b2c7436..f07b938 100644 --- a/tests/test_hallways.py +++ b/tests/test_hallways.py @@ -92,6 +92,24 @@ class TestHallwayStorage: class TestComputeHallways: + def test_explicit_config_scopes_persistence_to_selected_palace(self, tmp_path): + from mempalace.config import MempalaceConfig + + default_cfg = MempalaceConfig(palace_path=tmp_path / "default" / "palace") + selected_cfg = MempalaceConfig(palace_path=tmp_path / "selected" / "palace") + col = _fake_collection( + [ + {"wing": "wing_aya", "room": "diary", "entities": "Aya;Lumi"}, + {"wing": "wing_aya", "room": "letters", "entities": "Aya;Lumi"}, + ] + ) + + created = hallways_mod.compute_hallways_for_wing("wing_aya", col=col, config=selected_cfg) + + assert len(created) == 1 + assert hallways_mod.list_hallways(config=selected_cfg) == created + assert hallways_mod.list_hallways(config=default_cfg) == [] + def test_returns_empty_for_unknown_wing(self, tmp_path, monkeypatch): """Wing with no drawers → no hallways, no crash.""" _use_tmp_hallway_file(monkeypatch, tmp_path) diff --git a/tests/test_miner.py b/tests/test_miner.py index 02d021e..c917c90 100644 --- a/tests/test_miner.py +++ b/tests/test_miner.py @@ -95,8 +95,8 @@ def test_mine_computes_hallways_for_wing_post_mine(monkeypatch): hallway_calls = [] - def fake_compute(wing, col=None, min_count=2): - hallway_calls.append({"wing": wing, "col": col, "min_count": min_count}) + def fake_compute(wing, col=None, min_count=2, config=None): + hallway_calls.append({"wing": wing, "col": col, "min_count": min_count, "config": config}) return [] # no hallways materialized — that's not what we're testing # Patch at the call site (mempalace.miner.compute_hallways_for_wing) so @@ -134,6 +134,7 @@ def test_mine_computes_hallways_for_wing_post_mine(monkeypatch): assert call["col"] is not None, ( "must pass the live collection so hallways can query drawers" ) + assert call["config"].palace_path == str(palace_path) finally: shutil.rmtree(tmpdir, ignore_errors=True) @@ -147,7 +148,7 @@ def test_mine_hallway_failure_does_not_crash_mine(monkeypatch): """ from mempalace import miner as miner_mod - def angry_compute(wing, col=None, min_count=2): + def angry_compute(wing, col=None, min_count=2, config=None): raise RuntimeError("simulated hallway-compute explosion") monkeypatch.setattr(miner_mod, "compute_hallways_for_wing", angry_compute) @@ -194,8 +195,8 @@ def test_mine_computes_entity_tunnels_for_wing_post_mine(monkeypatch): entity_tunnel_calls = [] - def fake_compute(wing): - entity_tunnel_calls.append({"wing": wing}) + def fake_compute(wing, config=None): + entity_tunnel_calls.append({"wing": wing, "config": config}) return 0 # no tunnels — that's not what we're testing here # Patch at the call site (mempalace.miner._compute_entity_tunnels_for_wing) @@ -227,6 +228,7 @@ def test_mine_computes_entity_tunnels_for_wing_post_mine(monkeypatch): f"got {len(entity_tunnel_calls)}" ) assert entity_tunnel_calls[0]["wing"] == "test_project" + assert entity_tunnel_calls[0]["config"].palace_path == str(palace_path) finally: shutil.rmtree(tmpdir, ignore_errors=True) @@ -241,7 +243,7 @@ def test_mine_entity_tunnel_failure_does_not_crash_mine(monkeypatch): """ from mempalace import miner as miner_mod - def angry_compute(wing): + def angry_compute(wing, config=None): raise RuntimeError("simulated entity-tunnel-compute explosion") monkeypatch.setattr(miner_mod, "_compute_entity_tunnels_for_wing", angry_compute) diff --git a/tests/test_palace_graph_tunnels.py b/tests/test_palace_graph_tunnels.py index bd21cda..f96afd1 100644 --- a/tests/test_palace_graph_tunnels.py +++ b/tests/test_palace_graph_tunnels.py @@ -201,6 +201,21 @@ class TestTopicTunnels: dedup and persistence with explicit tunnels. """ + def test_explicit_config_scopes_persistence_to_selected_palace(self, tmp_path): + from mempalace.config import MempalaceConfig + + default_cfg = MempalaceConfig(palace_path=tmp_path / "default" / "palace") + selected_cfg = MempalaceConfig(palace_path=tmp_path / "selected" / "palace") + topics_by_wing = {"wing_alpha": ["OpenAPI"], "wing_beta": ["OpenAPI"]} + + created = palace_graph.compute_topic_tunnels( + topics_by_wing, min_count=1, config=selected_cfg + ) + + assert len(created) == 1 + assert palace_graph._load_tunnels(selected_cfg) == created + assert palace_graph._load_tunnels(default_cfg) == [] + def test_compute_topic_tunnels_creates_link_for_shared_topic(self, tmp_path, monkeypatch): _use_tmp_tunnel_file(monkeypatch, tmp_path) topics_by_wing = { From 287d8f1b4d5278ea2fbd3d56ce4feb235ad673df Mon Sep 17 00:00:00 2001 From: Igor Lins e Silva <4753812+igorls@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:40:29 -0300 Subject: [PATCH 2/2] fix: scope hallway reads and deletion to selected palace --- mempalace/cli.py | 10 +++++++++- mempalace/hallways.py | 6 +++--- tests/test_cli_hallways.py | 23 +++++++++++++++++++---- tests/test_hallways.py | 13 +++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/mempalace/cli.py b/mempalace/cli.py index 08b1586..5713669 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -1005,7 +1005,15 @@ def cmd_hallways(args): """List within-wing entity hallways (the auto-built associative graph).""" from .hallways import list_hallways - rows = list_hallways(getattr(args, "wing", None)) + palace_path = ( + os.path.expanduser(args.palace) + if getattr(args, "palace", None) + else MempalaceConfig().palace_path + ) + rows = list_hallways( + getattr(args, "wing", None), + config=MempalaceConfig(palace_path=palace_path), + ) if not rows: print("No hallways yet — they are built from drawer entities when you mine.") return diff --git a/mempalace/hallways.py b/mempalace/hallways.py index a4d3afa..cd079cf 100644 --- a/mempalace/hallways.py +++ b/mempalace/hallways.py @@ -383,11 +383,11 @@ def list_hallways(wing: Optional[str] = None, config=None) -> list[dict]: return [h for h in all_hallways if h.get("wing") == wing] -def delete_hallway(hallway_id: str) -> bool: +def delete_hallway(hallway_id: str, config=None) -> bool: """Remove one hallway record by id. Returns True if a record was removed.""" - hallways = _load_hallways() + hallways = _load_hallways(config) filtered = [h for h in hallways if h.get("id") != hallway_id] if len(filtered) == len(hallways): return False - _save_hallways(filtered) + _save_hallways(filtered, config) return True diff --git a/tests/test_cli_hallways.py b/tests/test_cli_hallways.py index 76739d2..863d9cc 100644 --- a/tests/test_cli_hallways.py +++ b/tests/test_cli_hallways.py @@ -23,7 +23,7 @@ def test_lists_sorted_by_count(monkeypatch, capsys): "label": "A <-> B (x3)", }, ] - monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None: list(rows)) + monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None, config=None: list(rows)) cmd_hallways(Namespace(wing=None, limit=50)) out = capsys.readouterr().out assert "2 hallway(s)" in out @@ -37,7 +37,7 @@ def test_respects_limit(monkeypatch, capsys): {"entity_a": f"E{i}", "entity_b": "X", "co_occurrence_count": i, "label": f"E{i} <-> X"} for i in range(5) ] - monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None: list(rows)) + monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None, config=None: list(rows)) cmd_hallways(Namespace(wing=None, limit=2)) assert capsys.readouterr().out.count("<->") == 2 @@ -47,13 +47,28 @@ def test_negative_limit_shows_nothing_not_tail(monkeypatch, capsys): {"entity_a": f"E{i}", "entity_b": "X", "co_occurrence_count": i, "label": f"E{i} <-> X"} for i in range(5) ] - monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None: list(rows)) + monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None, config=None: list(rows)) cmd_hallways(Namespace(wing=None, limit=-2)) # A negative limit must not slice from the end (which would print all-but-2). assert capsys.readouterr().out.count("<->") == 0 def test_empty_message(monkeypatch, capsys): - monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None: []) + monkeypatch.setattr(hallways_mod, "list_hallways", lambda wing=None, config=None: []) cmd_hallways(Namespace(wing="x", limit=50)) assert "No hallways yet" in capsys.readouterr().out + + +def test_explicit_palace_scopes_hallway_listing(monkeypatch, tmp_path): + calls = [] + + def fake_list(wing=None, config=None): + calls.append((wing, config.palace_path)) + return [] + + selected = tmp_path / "selected" / "palace" + monkeypatch.setattr(hallways_mod, "list_hallways", fake_list) + + cmd_hallways(Namespace(wing="wing_aya", limit=50, palace=str(selected))) + + assert calls == [("wing_aya", str(selected))] diff --git a/tests/test_hallways.py b/tests/test_hallways.py index f07b938..fa89597 100644 --- a/tests/test_hallways.py +++ b/tests/test_hallways.py @@ -318,6 +318,19 @@ class TestHallwayQuery: hallways_mod._save_hallways([{"id": "h1", "wing": "wing_aya"}]) assert hallways_mod.delete_hallway("nonexistent") is False + def test_delete_hallway_uses_selected_palace_config(self, tmp_path): + from mempalace.config import MempalaceConfig + + default_cfg = MempalaceConfig(palace_path=tmp_path / "default" / "palace") + selected_cfg = MempalaceConfig(palace_path=tmp_path / "selected" / "palace") + record = {"id": "h1", "wing": "wing_aya"} + hallways_mod._save_hallways([record], default_cfg) + hallways_mod._save_hallways([record], selected_cfg) + + assert hallways_mod.delete_hallway("h1", config=selected_cfg) is True + assert hallways_mod.list_hallways(config=selected_cfg) == [] + assert hallways_mod.list_hallways(config=default_cfg) == [record] + # ───────────────────────────────────────────────────────────────────────────── # L7 dynamics integration — hallway records carry strength/stability/etc