Skip to content

Commit d7ba64c

Browse files
committed
test(status): cover explicit-route resolution when default runtimes conflict
The production fix for explicit runtime routes shipped in main (#5436), so this PR now keeps only regression coverage for the shared resolver: - candidate discovery inspects conflicting default roots instead of failing, while implicit selection still refuses a broken default root; - configure-goal --execute syncs the single declared target through target_registry.goal.source_registry and writes no second authority; - two default roots declaring the same source are refused before any registry is touched. Signed-off-by: huangruiteng <huangrt01@163.com>
1 parent 0cbeda3 commit d7ba64c

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import json
2+
from pathlib import Path
3+
4+
import pytest
5+
6+
from canonical_authority_fixture import promoted_create_fixture
7+
from loopx import paths
8+
from loopx.cli import main
9+
from loopx.control_plane.effect_runtime import restart_effect_runtime
10+
from loopx.control_plane.runtime import runtime_projection_route
11+
from loopx.control_plane.runtime.runtime_projection_route import (
12+
runtime_projection_candidate_roots,
13+
)
14+
from loopx.status import collect_status
15+
16+
DEFAULT_CONFLICT_GOAL = "goal-a"
17+
18+
19+
def _conflicting_default_roots(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
20+
"""Two default roots with machine state, exactly as a migration leaves them."""
21+
22+
current, legacy = tmp_path / "current", tmp_path / "legacy"
23+
for root in (current, legacy):
24+
root.mkdir()
25+
(root / "machine.json").write_text("{}", encoding="utf-8")
26+
monkeypatch.setattr(paths, "DEFAULT_RUNTIME_ROOT", current)
27+
monkeypatch.setattr(paths, "LEGACY_RUNTIME_ROOT", legacy)
28+
return current, legacy
29+
30+
31+
def _source_registry(tmp_path: Path) -> Path:
32+
"""A disposable project registry for one Goal with its own runtime root."""
33+
34+
state = tmp_path / "ACTIVE_GOAL_STATE.md"
35+
state.write_text(
36+
"---\n"
37+
f"goal_id: {DEFAULT_CONFLICT_GOAL}\n"
38+
"handoff_mode: hard_lease\n"
39+
"---\n\n"
40+
"## Agent Todo\n\n",
41+
encoding="utf-8",
42+
)
43+
runtime = tmp_path / "project-runtime"
44+
runtime.mkdir()
45+
registry = tmp_path / "project" / "registry.json"
46+
registry.parent.mkdir(parents=True)
47+
registry.write_text(
48+
json.dumps(
49+
{
50+
"schema_version": 1,
51+
"common_runtime_root": str(runtime),
52+
"goals": [
53+
{
54+
"id": DEFAULT_CONFLICT_GOAL,
55+
"repo": str(tmp_path),
56+
"state_file": state.name,
57+
"coordination": {"agent_model": "peer_v1", "registered_agents": ["agent-a"]},
58+
}
59+
],
60+
}
61+
),
62+
encoding="utf-8",
63+
)
64+
return registry
65+
66+
67+
def _declare_target(root: Path, source_registry: Path) -> Path:
68+
"""Write the global registry that names this source registry as the target's author."""
69+
70+
target = paths.global_registry_path(root)
71+
source_payload = json.loads(source_registry.read_text(encoding="utf-8"))
72+
goal = dict(source_payload["goals"][0])
73+
goal["source_registry"] = str(source_registry.resolve())
74+
target.write_text(
75+
json.dumps({"schema_version": 1, "registry_role": "global-local", "goals": [goal]}),
76+
encoding="utf-8",
77+
)
78+
return target
79+
80+
81+
def _configure(registry: Path, capsys) -> tuple[int, dict]:
82+
code = main(
83+
[
84+
"--registry",
85+
str(registry),
86+
"--format",
87+
"json",
88+
"configure-goal",
89+
"--goal-id",
90+
DEFAULT_CONFLICT_GOAL,
91+
"--quota-window-hours",
92+
"12",
93+
"--execute",
94+
]
95+
)
96+
captured = capsys.readouterr()
97+
payload = json.loads(captured.out) if captured.out.strip().startswith("{") else {}
98+
return code, payload
99+
100+
101+
def test_explicit_status_survives_conflicting_defaults(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
102+
current, legacy = tmp_path / "current", tmp_path / "legacy"
103+
for root in (current, legacy):
104+
root.mkdir()
105+
(root / "machine.json").write_text("{}", encoding="utf-8")
106+
monkeypatch.setattr(paths, "DEFAULT_RUNTIME_ROOT", current)
107+
monkeypatch.setattr(paths, "LEGACY_RUNTIME_ROOT", legacy)
108+
registry, runtime, _state = promoted_create_fixture(tmp_path / "fixture")
109+
try:
110+
projection = collect_status(
111+
registry_path=registry,
112+
runtime_root_override=str(runtime),
113+
scan_roots=[],
114+
limit=10,
115+
goal_id="goal-a",
116+
include_public_boundary_scan=False,
117+
)
118+
assert any(goal["id"] == "goal-a" for goal in projection["run_history"]["goals"])
119+
assert set(runtime_projection_candidate_roots(source_runtime_root=runtime)) == {
120+
current, legacy, runtime,
121+
}
122+
# Diagnostic discovery never authorizes implicit execution or migration.
123+
with pytest.raises(ValueError, match="Both default LoopX runtime roots"):
124+
paths.select_default_runtime_root()
125+
assert (current / "machine.json").read_text() == "{}"
126+
assert (legacy / "machine.json").read_text() == "{}"
127+
finally:
128+
restart_effect_runtime()
129+
130+
131+
def test_explicit_candidates_do_not_discover_other_roots(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
132+
def unexpected_read():
133+
pytest.fail("Explicit candidate roots must remain isolated")
134+
135+
monkeypatch.setattr(runtime_projection_route, "default_runtime_route", unexpected_read, raising=False)
136+
root = tmp_path / "explicit"
137+
assert runtime_projection_candidate_roots(
138+
source_runtime_root=root, candidate_roots=[root, root],
139+
) == [root]
140+
141+
142+
def test_invalid_default_root_is_not_treated_as_a_conflict(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
143+
current = tmp_path / "invalid"
144+
current.write_text("not a directory", encoding="utf-8")
145+
monkeypatch.setattr(paths, "DEFAULT_RUNTIME_ROOT", current)
146+
monkeypatch.setattr(paths, "LEGACY_RUNTIME_ROOT", tmp_path / "absent")
147+
explicit = tmp_path / "explicit"
148+
# Discovery stays a candidate read: an invalid default root is inspected, not
149+
# selected. Implicit selection keeps refusing it, so the read-only path cannot
150+
# hide a broken route from the commands that must not guess.
151+
assert runtime_projection_candidate_roots(source_runtime_root=explicit) == [
152+
current.resolve(),
153+
(tmp_path / "absent").resolve(),
154+
explicit.resolve(),
155+
]
156+
with pytest.raises(ValueError, match="linked or not a directory"):
157+
paths.select_default_runtime_root()
158+
assert current.read_text(encoding="utf-8") == "not a directory"
159+
160+
161+
def test_configure_goal_execute_syncs_the_single_declared_target(
162+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str],
163+
):
164+
current, legacy = _conflicting_default_roots(tmp_path, monkeypatch)
165+
registry = _source_registry(tmp_path)
166+
target = _declare_target(legacy, registry)
167+
168+
code, payload = _configure(registry, capsys)
169+
170+
assert code == 0, json.dumps(payload, indent=1)
171+
assert payload.get("ok") is True
172+
sync = payload.get("global_sync") or {}
173+
resolution = sync.get("target_resolution") or {}
174+
assert resolution.get("target_global_registry") == str(target), json.dumps(sync, indent=1)
175+
assert resolution.get("declaration_source") == "target_registry.goal.source_registry"
176+
assert (resolution.get("route") or {}).get("status") == "resolved"
177+
configured = json.loads(registry.read_text(encoding="utf-8"))["goals"][0]
178+
assert configured["quota"]["window_hours"] == 12
179+
# The conflicting root stays an untouched candidate, not a second writer.
180+
assert not paths.global_registry_path(current).exists()
181+
assert (current / "machine.json").read_text(encoding="utf-8") == "{}"
182+
183+
184+
def test_configure_goal_execute_refuses_ambiguous_declared_targets(
185+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str],
186+
):
187+
current, legacy = _conflicting_default_roots(tmp_path, monkeypatch)
188+
registry = _source_registry(tmp_path)
189+
_declare_target(legacy, registry)
190+
_declare_target(current, registry)
191+
before = registry.read_bytes()
192+
193+
code, payload = _configure(registry, capsys)
194+
195+
assert code != 0
196+
assert "ambiguous" in json.dumps(payload)
197+
assert registry.read_bytes() == before

0 commit comments

Comments
 (0)