ouroboros/tests/test_evolution_state_integrity_v3.py
Anton Razzhigaev b9ceed6ed7 merge: absorb upstream f3fbfdbb into the v7next campaign tree
Rolling-upstream sync #2 (8d13373b..f3fbfdbb, 101 commits, 180 files) under
the standing principle: upstream is the SEMANTIC truth, the campaign is the
STRUCTURAL truth. Every upstream semantic delta is re-seated in the campaign
leaf that owns its span; the campaign's split shape, typed organs and ratified
contracts are preserved.

Notable seats: registry post-exec organ (owner-state restore DELETED, replaced
by the settings tripwire) -> registry_guard_process/registry_core; the #447 H1
notes-trail-the-payload contract -> tool_result composition; the В23=A owner-home
read carve + egress masking -> tool_access_user_files/core_file_tools; the #468
shape-first reasoning pin -> llm_messages/llm_fallback/llm_attempt/
llm_openai_compatible; delivery-control provenance and the forced-control body
resolver -> loop_delivery/loop_forced_finalization; D4 export policy ->
shell_outputs; A5 literal-argv disclosure -> tools/shell.

Upstream twins of campaign organs do not live: tools/read_inspection.py folds
into registry_guard_process, tools/result_envelope.py into tools/tool_result,
delivery_protocol.py stays folded in loop_delivery.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-01 20:26:20 +00:00

218 lines
8.6 KiB
Python

"""The disposable state root a pytest process must use, and the fuse over the live one.
This module owns the isolation invariants every other evolution suite depends on: the
process globals resolve to a disposable root, the fuse blocks state and campaign writes to
the live data dir, the bootstrap rebinds preimported modules away from a fake home, a
scrubbed child keeps the disposable root, and a nested pytest keeps the original live-root
marker.
The scheduler, terminal events, commit receipts, publication and restart claims were split
verbatim into ``tests/test_evolution_scheduler.py``,
``tests/test_evolution_terminal_events.py``, ``tests/test_evolution_commit_receipt.py``,
``tests/test_evolution_publication.py`` and ``tests/test_evolution_restart_claims.py``; the
transaction builder, commit seam and capture queue they share live in
``tests/_evolution_state_shared.py``.
"""
from __future__ import annotations
import json
import os
import pathlib
import subprocess
import sys
import pytest
def test_pytest_process_globals_use_the_disposable_root():
from supervisor import git_ops, queue, state, workers
expected = pathlib.Path(os.environ["OUROBOROS_DATA_DIR"]).resolve(strict=False)
assert state.DRIVE_ROOT.resolve(strict=False) == expected
assert queue.DRIVE_ROOT.resolve(strict=False) == expected
assert git_ops.DRIVE_ROOT.resolve(strict=False) == expected
assert workers.DRIVE_ROOT.resolve(strict=False) == expected
assert expected != (pathlib.Path.home() / "Ouroboros" / "data").resolve(strict=False)
def test_pytest_fuse_blocks_state_and_campaign_writes_to_live_data(monkeypatch):
from supervisor import evolution_lifecycle, queue, state
live = pathlib.Path(os.environ["OUROBOROS_TEST_LIVE_DATA_ROOT"])
monkeypatch.setattr(state, "STATE_PATH", live / "state" / "state.json")
monkeypatch.setattr(state, "STATE_LOCK_PATH", live / "locks" / "state.lock")
with pytest.raises(RuntimeError, match="PYTEST_LIVE_DATA_WRITE_BLOCKED"):
state.save_state({"evolution_mode_enabled": True})
monkeypatch.setattr(queue, "DRIVE_ROOT", live)
with pytest.raises(RuntimeError, match="PYTEST_LIVE_DATA_WRITE_BLOCKED"):
evolution_lifecycle._write_evolution_campaign({"id": "blocked", "status": "active"})
@pytest.mark.serial
def test_pytest_bootstrap_rebinds_preimported_modules_away_from_fake_home(tmp_path):
repo = pathlib.Path(__file__).resolve().parents[1]
fake_home = tmp_path / "home"
sentinel_path = fake_home / "Ouroboros" / "data" / "state" / "state.json"
sentinel_path.parent.mkdir(parents=True)
sentinel = b'{"sentinel":"live"}\n'
sentinel_path.write_bytes(sentinel)
env = dict(os.environ)
for key in (
"OUROBOROS_DATA_DIR",
"OUROBOROS_SETTINGS_PATH",
"OUROBOROS_PYTEST_ACTIVE",
"OUROBOROS_TEST_LIVE_DATA_ROOT",
):
env.pop(key, None)
env["HOME"] = str(fake_home)
env["USERPROFILE"] = str(fake_home)
env["PYTHONPATH"] = os.pathsep.join((str(repo), str(repo / "tests")))
code = """
import importlib.util, json, pathlib
from supervisor import git_ops, queue, state, workers
spec = importlib.util.spec_from_file_location('isolated_conftest', pathlib.Path('tests/conftest.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module._bind_pytest_runtime_roots()
state.update_state(lambda live: live.update({'probe': 'isolated'}))
print(json.dumps({
'root': str(state.DRIVE_ROOT),
'git_ops_root': str(git_ops.DRIVE_ROOT),
'state': state.load_state(),
}))
"""
proc = subprocess.run(
[sys.executable, "-c", code],
cwd=repo,
env=env,
check=True,
capture_output=True,
text=True,
)
payload = json.loads(proc.stdout.strip().splitlines()[-1])
assert sentinel_path.read_bytes() == sentinel
assert pathlib.Path(payload["root"]) != sentinel_path.parents[1]
assert payload["git_ops_root"] == payload["root"]
assert payload["state"]["probe"] == "isolated"
@pytest.mark.serial
def test_pytest_scrubbed_child_keeps_disposable_state_root(tmp_path):
repo = pathlib.Path(__file__).resolve().parents[1]
fake_home = tmp_path / "home"
fake_live = fake_home / "Ouroboros" / "data" / "state"
fake_live.mkdir(parents=True)
sentinels = {
fake_live / "state.json": b'{"sentinel":"state"}\n',
fake_live / "evolution_campaign.json": b'{"sentinel":"campaign"}\n',
}
for path, content in sentinels.items():
path.write_bytes(content)
disposable_state = pathlib.Path(os.environ["OUROBOROS_DATA_DIR"]) / "state"
disposable_paths = tuple(
disposable_state / name
for name in ("state.json", "state.last_good.json", "evolution_campaign.json")
)
disposable_before = {
path: path.read_bytes() if path.exists() else None for path in disposable_paths
}
code = """
import json
from supervisor import evolution_lifecycle, state
state.update_state(lambda live: live.update({'scrubbed_child_probe': True}))
campaign = evolution_lifecycle.start_evolution_campaign('Probe', source='test')
print(json.dumps({
'campaign_id': campaign.get('id', ''),
'drive_root': str(state.DRIVE_ROOT),
'pytest_loaded': 'pytest' in __import__('sys').modules,
}))
"""
child_env = {"HOME": str(fake_home), "USERPROFILE": str(fake_home)}
if os.name == "nt" and os.environ.get("SystemRoot"):
child_env["SystemRoot"] = os.environ["SystemRoot"]
try:
proc = subprocess.run(
[sys.executable, "-c", code],
cwd=repo,
env=child_env,
check=True,
capture_output=True,
text=True,
)
payload = json.loads(proc.stdout.strip().splitlines()[-1])
assert payload["pytest_loaded"] is False
assert pathlib.Path(payload["drive_root"]).resolve(strict=False) == pathlib.Path(
os.environ["OUROBOROS_DATA_DIR"]
).resolve(strict=False)
assert payload["campaign_id"]
for path, content in sentinels.items():
assert path.read_bytes() == content
finally:
for path, content in disposable_before.items():
if content is None:
path.unlink(missing_ok=True)
else:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
@pytest.mark.serial
def test_nested_pytest_keeps_the_original_live_root_marker(tmp_path):
repo = pathlib.Path(__file__).resolve().parents[1]
inherited_disposable = tmp_path / "parent-pytest-data"
env = {
"OUROBOROS_DATA_DIR": str(inherited_disposable),
"OUROBOROS_SETTINGS_PATH": str(inherited_disposable / "settings.json"),
}
# A Windows child python cannot even boot without SystemRoot, and the nested
# conftest's fresh mkdtemp needs a real TEMP (the ntpath fallback chain would
# otherwise land in the repo cwd). POSIX children boot fine with a bare env —
# same passthrough precedent as the scrubbed-child test above. The conftest
# Popen patch injects the OUROBOROS_* markers this test is actually about.
if os.name == "nt":
# USERPROFILE too: the conftest now resolves the canonical home root,
# and ntpath's expanduser chain ignores HOME entirely.
for key in ("SystemRoot", "TEMP", "TMP", "USERPROFILE"):
if os.environ.get(key):
env[key] = os.environ[key]
code = """
import importlib.util, json, os, pathlib
spec = importlib.util.spec_from_file_location('nested_conftest', pathlib.Path('tests/conftest.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
print(json.dumps({
'live_root': os.environ['OUROBOROS_TEST_LIVE_DATA_ROOT'],
'data_root': os.environ['OUROBOROS_DATA_DIR'],
}))
"""
try:
proc = subprocess.run(
[sys.executable, "-c", code],
cwd=repo,
env=env,
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as exc:
# A bare exit-1 from a CI runner is undiagnosable (the probe class);
# name the child's own words.
raise AssertionError(
f"nested conftest child failed (exit {exc.returncode}); "
f"stderr tail: {(exc.stderr or '')[-2000:]!r}"
) from exc
payload = json.loads(proc.stdout.strip().splitlines()[-1])
assert pathlib.Path(payload["live_root"]).resolve(strict=False) == pathlib.Path(
os.environ["OUROBOROS_TEST_LIVE_DATA_ROOT"]
).resolve(strict=False)
assert pathlib.Path(payload["data_root"]).resolve(strict=False) != inherited_disposable.resolve(
strict=False
)