Skip to content

Commit de1ae49

Browse files
authored
Merge pull request #5474 from loopx-project/codex/test-home-isolation
2 parents 8398bc4 + 64fda42 commit de1ae49

8 files changed

Lines changed: 94 additions & 5 deletions

‎.github/workflows/postgresql-integration.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ permissions:
3131

3232
concurrency:
3333
group: postgresql-integration-${{ github.ref }}
34-
cancel-in-progress: true
34+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
3535

3636
jobs:
3737
postgresql-authority:

‎.github/workflows/python-tests.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ permissions:
3030

3131
concurrency:
3232
group: python-tests-${{ github.ref }}
33-
cancel-in-progress: true
33+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
3434

3535
env:
3636
LOOPX_USAGE_PING: "0"

‎loopx/paths.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ def _is_route_observation(path: Path) -> bool:
147147
return False
148148
if path.name == GLOBAL_REGISTRY_FILENAME + ".lock":
149149
return path.is_file()
150+
# The native effect runtime locks every registry it writes through, so a
151+
# mere lock file must not turn a default root into a second authority.
152+
# Spelled here because file_lock imports this module.
153+
if path.name == GLOBAL_REGISTRY_FILENAME + ".ts-effect.lock":
154+
return path.is_file()
150155
if path.name == "lark-consumers" and path.is_dir():
151156
import re
152157

‎loopx/semantics/project_registry_io_manifest_v1.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1959,7 +1959,7 @@
19591959
},
19601960
{
19611961
"site": "loopx/paths.py::<module>.configured_runtime_route::codec_read:load_registry#1",
1962-
"line": 262,
1962+
"line": 267,
19631963
"column": 20,
19641964
"kind": "codec_read",
19651965
"api": "load_registry",

‎tests/conftest.py‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import importlib
34
import os
45
import sys
56
from pathlib import Path
@@ -12,10 +13,65 @@
1213
if str(REPO_ROOT) not in sys.path:
1314
sys.path.insert(0, str(REPO_ROOT))
1415

16+
import pytest # noqa: E402
17+
18+
from loopx import paths # noqa: E402
1519
from loopx.canary.runner import SMOKE_SUITE_CHOICES # noqa: E402
1620
from loopx.semantics.production import NPM_DEV_DEPENDENCIES_MISSING # noqa: E402
1721

1822

23+
# Default runtime routes resolve from HOME at import time. A test that writes
24+
# either one leaves state behind, and once both hold state every later implicit
25+
# default route fails as a conflict, so each test gets its own disposable pair.
26+
_DEFAULT_ROUTE_REFERENCES = (
27+
("loopx.paths", "DEFAULT_RUNTIME_ROOT", (".loopx",)),
28+
("loopx.paths", "LEGACY_RUNTIME_ROOT", (".codex", "loopx")),
29+
("loopx.contract", "DEFAULT_RUNTIME_ROOT", (".loopx",)),
30+
("loopx.contract", "LEGACY_RUNTIME_ROOT", (".codex", "loopx")),
31+
("loopx.cli_commands.registry_admin_lifecycle", "DEFAULT_RUNTIME_ROOT", (".loopx",)),
32+
("loopx.cli_commands.registry_admin_lifecycle", "LEGACY_LOCAL_RUNTIME_ROOT", (".codex", "loopx")),
33+
("loopx.control_plane.runtime.local_state_migration", "DEFAULT_RUNTIME_ROOT", (".loopx",)),
34+
("loopx.control_plane.runtime.local_state_migration", "LEGACY_RUNTIME_ROOT", (".codex", "loopx")),
35+
)
36+
_REAL_DEFAULT_ROUTES = (paths.DEFAULT_RUNTIME_ROOT, paths.LEGACY_RUNTIME_ROOT)
37+
38+
39+
@pytest.fixture(autouse=True)
40+
def _isolated_default_runtime_routes(tmp_path_factory, monkeypatch):
41+
home = tmp_path_factory.mktemp("home")
42+
monkeypatch.setenv("HOME", str(home))
43+
monkeypatch.setenv("USERPROFILE", str(home))
44+
monkeypatch.delenv("CODEX_HOME", raising=False)
45+
for module_name, attribute, parts in _DEFAULT_ROUTE_REFERENCES:
46+
monkeypatch.setattr(importlib.import_module(module_name), attribute, home.joinpath(*parts))
47+
yield
48+
49+
50+
def pytest_sessionstart(session) -> None:
51+
# Only routes absent at start are guarded; existing developer state may be
52+
# changed by other local processes during the run.
53+
session.config._loopx_absent_routes = [
54+
root for root in _REAL_DEFAULT_ROUTES if not os.path.lexists(root)
55+
]
56+
57+
58+
def pytest_sessionfinish(session, exitstatus) -> None:
59+
created = [
60+
root for root in getattr(session.config, "_loopx_absent_routes", [])
61+
if os.path.lexists(root)
62+
]
63+
if not created:
64+
return
65+
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
66+
if reporter is not None:
67+
reporter.write_sep("=", "loopx default runtime route leak", red=True)
68+
reporter.write_line(
69+
f"Tests created real default runtime routes {', '.join(map(str, created))}; "
70+
"isolate the writer."
71+
)
72+
session.exitstatus = pytest.ExitCode.TESTS_FAILED
73+
74+
1975
def pytest_addoption(parser) -> None:
2076
group = parser.getgroup("loopx-smoke-suite")
2177
group.addoption(

‎tests/test_chat_codex_goal.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,11 @@ def test_external_queued_message_cannot_activate_local_owner_continuation(
415415
message="/goal start --tokens 1000 Analyze",
416416
origin="lark",
417417
)
418+
# Production dispatch claims the exact queued Turn before starting it.
419+
# Without that claim the execution fence correctly refuses this worker
420+
# before it can evaluate whether the external command is authorized.
421+
claimed = store.claim_next_queued_turn(session["session_id"])
422+
assert claimed is not None and claimed["turn_id"] == turn["turn_id"]
418423
controller._run_turn(
419424
session_id=session["session_id"],
420425
turn_id=turn["turn_id"],

‎tests/test_local_state_migration.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,29 @@ def reparse_lstat(path, *args, **kwargs):
159159
assert paths.configured_runtime_route(runtime_root_override=str(redirected))["status"] == "invalid"
160160

161161

162+
@pytest.mark.parametrize("relative", ["registry.global.json.lock",
163+
"registry.global.json.ts-effect.lock"])
164+
def test_own_registry_locks_do_not_declare_a_second_authority(
165+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, relative: str,
166+
) -> None:
167+
"""A route must not read its own write-locks back as machine state.
168+
169+
Goal configuration locks every candidate runtime registry before writing,
170+
and the native effect runtime locks the same file too. On a fresh machine
171+
those locks are the only entries in the untouched legacy root, so treating
172+
them as state made the first configured Goal fail as a two-root conflict.
173+
"""
174+
175+
source, target = tmp_path / "home" / ".codex" / "loopx", tmp_path / "home" / ".loopx"
176+
monkeypatch.setattr(paths, "LEGACY_RUNTIME_ROOT", source)
177+
monkeypatch.setattr(paths, "DEFAULT_RUNTIME_ROOT", target)
178+
for root in (source, target):
179+
root.mkdir(parents=True)
180+
(root / relative).touch()
181+
assert paths.default_runtime_route()["status"] == "fresh"
182+
assert paths.select_default_runtime_root() == target
183+
184+
162185
def test_global_service_selector_keeps_a_registered_route_amid_real_conflict(tmp_path, monkeypatch):
163186
from loopx.cli_commands.support_control_registry import explicit_global_registry
164187
source, target, projects = _fixture(tmp_path, projects=1)

‎tests/test_skill_delivery_parity.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ def test_packaged_skill_dirs_exist(self):
8282
encoding="utf-8"
8383
).strip() == "global", skill_id
8484

85-
def test_repo_has_seven_skills(self):
86-
assert len(REQUIRED_HOST_SKILL_IDS) == 7 # loopx + 6 packaged
85+
def test_required_skills_match_the_shipped_catalog(self):
86+
assert set(REQUIRED_HOST_SKILL_IDS) == {"loopx", *PACKAGED_HOST_SKILL_IDS}
8787

8888

8989
# -- Skill install readback lifecycle -----------------------------------------

0 commit comments

Comments
 (0)