ouroboros/tests/test_evolution_commit_receipt.py
Ouroboros c2e45111fb fix: restore portable release checks and symlink diagnostics
Preserve declared-entry hash failures and typed evidence omissions under Python 3.13. Keep the approved Windows artifact refusal explicit, isolate custody and nested pytest state, and synchronize the permission, lock, CSP, TLS and dispatcher fixtures without changing runtime policy.
2026-09-26 23:45:06 +03:00

393 lines
15 KiB
Python

"""The exact commit receipt: what binds it, what may read it, and what may never erase it.
Split out of ``tests/test_evolution_state_integrity_v3.py`` by theme: the receipt bound to
campaign, transaction and task; the second commit blocked before review; the receipt race
ahead of the git commit; revoked authority; the rescue link and campaign sidecar that share
the CAS; and the stale, terminal and panicking writers that must not overwrite it.
"""
from __future__ import annotations
import pathlib
import threading
from types import SimpleNamespace
import pytest
from tests._evolution_state_shared import (
_active_transaction,
_patch_commit_seam,
)
def test_exact_commit_receipt_is_bound_to_campaign_transaction_and_task(tmp_path):
from supervisor import evolution_lifecycle
campaign, tx = _active_transaction(tmp_path)
claim = {
"campaign_id": campaign["id"],
"transaction_id": tx["transaction_id"],
"task_id": tx["task_id"],
}
assert evolution_lifecycle.check_evolution_authority(**claim)["ok"] is True
receipt = evolution_lifecycle.record_evolution_commit(**claim, commit_sha="a" * 40)
assert receipt["ok"] is True
assert receipt["commit_sha"] == "a" * 40
stored = evolution_lifecycle._read_evolution_campaign()["active_transaction"]
assert stored["commit_receipt"] == receipt
assert evolution_lifecycle.check_evolution_authority(
**claim, commit_sha="b" * 40,
)["reason"] == "commit_receipt_mismatch"
campaign_state = evolution_lifecycle._read_evolution_campaign()
campaign_state["active_transaction"].pop("commit_receipt")
assert evolution_lifecycle._write_evolution_campaign(campaign_state) is True
assert evolution_lifecycle.check_evolution_authority(
**claim, commit_sha="a" * 40,
)["reason"] == "commit_receipt_missing"
def test_second_evolution_commit_is_blocked_before_review(tmp_path, monkeypatch):
from ouroboros.tools import git as git_tools
from supervisor import evolution_lifecycle
campaign, tx = _active_transaction(tmp_path)
assert evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], "a" * 40,
)["ok"] is True
review_calls = []
monkeypatch.setattr(git_tools, "_task_attributed_commit_paths", lambda *a, **k: (None, None, "", None))
monkeypatch.setattr(git_tools, "_check_overlapping_review_attempt", lambda *a, **k: "")
_patch_commit_seam(monkeypatch, "_record_commit_attempt", lambda *a, **k: None)
monkeypatch.setattr(git_tools, "_acquire_git_lock", lambda *a, **k: pathlib.Path("lock"))
monkeypatch.setattr(git_tools, "_release_git_lock", lambda *a, **k: None)
monkeypatch.setattr(git_tools, "_prepare_review_commit_worktree", lambda *a, **k: (False, ""))
_patch_commit_seam(monkeypatch, "_run_reviewed_stage_cycle",
lambda *a, **k: review_calls.append(True) or {"status": "passed"},
)
ctx = SimpleNamespace(
repo_dir=tmp_path,
drive_root=tmp_path,
current_task_type="evolution",
task_id=tx["task_id"],
task_metadata={"evolution_transaction": tx},
)
result = git_tools._repo_commit_push(ctx, "second commit")
assert "transaction_already_committed" in result
assert "No reviewer was called" in result
assert review_calls == []
def test_receipt_race_blocks_evolution_before_git_commit(tmp_path, monkeypatch):
from ouroboros.tools import git as git_tools
from supervisor import evolution_lifecycle
campaign, tx = _active_transaction(tmp_path)
ctx = SimpleNamespace(
repo_dir=tmp_path,
current_task_type="evolution",
task_id=tx["task_id"],
task_metadata={"evolution_transaction": tx},
)
_patch_commit_seam(monkeypatch, "_record_commit_attempt", lambda *a, **k: None)
claim, error = git_tools._check_evolution_commit_stage(
ctx, "commit", 0.0, phase="pre_review_authority",
)
assert error == ""
assert evolution_lifecycle.record_evolution_commit(
**claim, commit_sha="b" * 40,
)["ok"] is True
_claim, error = git_tools._check_evolution_commit_stage(
ctx, "commit", 0.0, phase="pre_commit_authority",
)
assert "transaction_already_committed" in error
assert "Nothing was committed" in error
def test_revoked_authority_leaves_commit_unrecorded(tmp_path):
from supervisor import evolution_lifecycle, state
campaign, tx = _active_transaction(tmp_path)
live = state.load_state()
live["evolution_mode_enabled"] = False
live["evolution_owner_stopped"] = True
state.save_state(live)
receipt = evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], "c" * 40,
)
assert receipt == {"ok": False, "reason": "owner_stopped", "commit_sha": "c" * 40}
assert evolution_lifecycle._read_evolution_campaign()["active_transaction"]["commit_sha"] == ""
def test_exact_receipt_remains_authority_after_post_task_autostop(tmp_path):
from supervisor import evolution_lifecycle, state
campaign, tx = _active_transaction(tmp_path)
sha = "9" * 40
claim = {
"campaign_id": campaign["id"],
"transaction_id": tx["transaction_id"],
"task_id": tx["task_id"],
}
assert evolution_lifecycle.record_evolution_commit(**claim, commit_sha=sha)["ok"] is True
state.update_state(lambda live: live.update(
evolution_mode_enabled=False,
post_task_autostop=False,
))
assert evolution_lifecycle.check_evolution_authority(
**claim, commit_sha=sha,
)["ok"] is True
assert evolution_lifecycle.check_evolution_authority(**claim)["reason"] == "evolution_disabled"
@pytest.mark.parametrize("held_lock", ["state", "campaign"])
def test_rescue_link_uses_shared_campaign_cas_and_preserves_commit_receipt(
tmp_path, monkeypatch, held_lock,
):
from ouroboros import platform_layer
from ouroboros.platform_layer import (
acquire_exclusive_file_lock,
release_exclusive_file_lock,
)
from ouroboros.utils import atomic_write_json
from supervisor import evolution_lifecycle, git_ops, state
campaign, tx = _active_transaction(tmp_path)
sha = "3" * 40
assert evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], sha,
)["ok"] is True
monkeypatch.setattr(evolution_lifecycle, "EVOLUTION_CAMPAIGN_CAS_TIMEOUT_SEC", 1.0)
monkeypatch.setattr(git_ops, "DRIVE_ROOT", tmp_path)
campaign_path = tmp_path / "state" / "evolution_campaign.json"
if held_lock == "state":
lock_path = tmp_path / "locks" / "state.lock"
lock_fd = state.acquire_file_lock(lock_path, timeout_sec=1.0)
release = state.release_file_lock
else:
lock_path = campaign_path.with_name(campaign_path.name + ".lock")
lock_fd = acquire_exclusive_file_lock(lock_path, timeout_sec=1.0)
release = release_exclusive_file_lock
assert lock_fd is not None
done = threading.Event()
acquiring = threading.Event()
released = threading.Event()
lock_owner = state if held_lock == "state" else platform_layer
acquire = lock_owner.acquire_exclusive_file_lock
def _acquire_after_interleave(path, **kwargs):
if path == lock_path:
acquiring.set()
released.wait()
return acquire(path, **kwargs)
# Prove contention separately, then hand off at the real acquisition seam.
# File I/O and thread scheduling must not consume the CAS timeout while the
# fixture deliberately holds the lock needed by the successful update.
monkeypatch.setattr(lock_owner, "acquire_exclusive_file_lock", _acquire_after_interleave)
def _link() -> None:
git_ops._link_rescue_to_evolution_transaction(
{"rescue_ref": "rescue/test", "path": "/tmp/rescue-test"},
"test",
)
done.set()
thread = threading.Thread(target=_link, daemon=True)
thread.start()
try:
assert acquire_exclusive_file_lock(lock_path, timeout_sec=0.001) is None
assert acquiring.wait(2.0) is True
assert done.wait(0.1) is False
current = evolution_lifecycle._read_evolution_campaign()
current["active_transaction"]["interleaved"] = held_lock
atomic_write_json(campaign_path, current, trailing_newline=True)
finally:
release(lock_path, lock_fd)
released.set()
thread.join(timeout=1.0)
assert done.wait(2.0) is True
assert not thread.is_alive()
stored = evolution_lifecycle._read_evolution_campaign()["active_transaction"]
assert stored["commit_sha"] == sha
assert stored["commit_receipt"]["commit_sha"] == sha
assert stored["rescue_ref"] == "rescue/test"
assert stored["interleaved"] == held_lock
def test_commit_receipt_uses_campaign_sidecar_before_rescue(tmp_path, monkeypatch):
from ouroboros import platform_layer
from ouroboros.platform_layer import (
acquire_exclusive_file_lock,
release_exclusive_file_lock,
)
from supervisor import evolution_lifecycle
campaign, tx = _active_transaction(tmp_path)
monkeypatch.setattr(evolution_lifecycle, "EVOLUTION_CAMPAIGN_CAS_TIMEOUT_SEC", 1.0)
campaign_path = tmp_path / "state" / "evolution_campaign.json"
lock_path = campaign_path.with_name(campaign_path.name + ".lock")
lock_fd = acquire_exclusive_file_lock(lock_path, timeout_sec=1.0)
assert lock_fd is not None
done = threading.Event()
acquiring = threading.Event()
released = threading.Event()
result = {}
def _acquire_after_release(path, **kwargs):
if path == lock_path:
acquiring.set()
released.wait()
return acquire_exclusive_file_lock(path, **kwargs)
# Keep real contention, but start the acquisition timeout after the fixture
# releases its lock, as in the rescue interleaving test above.
monkeypatch.setattr(platform_layer, "acquire_exclusive_file_lock", _acquire_after_release)
def _record() -> None:
result.update(evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], "4" * 40,
))
done.set()
thread = threading.Thread(target=_record, daemon=True)
thread.start()
try:
assert acquire_exclusive_file_lock(lock_path, timeout_sec=0.001) is None
assert acquiring.wait(2.0) is True
assert done.wait(0.1) is False
finally:
release_exclusive_file_lock(lock_path, lock_fd)
released.set()
thread.join(timeout=2.0)
assert done.wait(2.0) is True
assert not thread.is_alive()
assert result["ok"] is True
assert evolution_lifecycle._read_evolution_campaign()["active_transaction"][
"commit_receipt"
]["commit_sha"] == "4" * 40
def test_campaign_sidecar_contention_releases_state_lock_quickly(tmp_path):
from ouroboros.platform_layer import (
acquire_exclusive_file_lock,
release_exclusive_file_lock,
)
from supervisor import evolution_lifecycle, state
campaign, tx = _active_transaction(tmp_path)
campaign_path = tmp_path / "state" / "evolution_campaign.json"
sidecar = campaign_path.with_name(campaign_path.name + ".lock")
sidecar_fd = acquire_exclusive_file_lock(sidecar, timeout_sec=1.0)
assert sidecar_fd is not None
try:
result = evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], "6" * 40,
)
assert result["ok"] is False
state_fd = state.acquire_file_lock(state.STATE_LOCK_PATH, timeout_sec=0.2)
assert state_fd is not None
state.release_file_lock(state.STATE_LOCK_PATH, state_fd)
finally:
release_exclusive_file_lock(sidecar, sidecar_fd)
def test_sent_owner_report_clear_cannot_erase_concurrent_commit_receipt(tmp_path, monkeypatch):
from supervisor import evolution_lifecycle, queue
campaign, tx = _active_transaction(tmp_path)
report = {"cycle_outcome": "absorbed", "task_id": "previous"}
current = evolution_lifecycle._read_evolution_campaign()
current["pending_owner_report"] = report
assert evolution_lifecycle._write_evolution_campaign(current) is True
def _send_then_record(*args, **kwargs):
receipt = evolution_lifecycle.record_evolution_commit(
campaign["id"], tx["transaction_id"], tx["task_id"], "5" * 40,
)
assert receipt["ok"] is True
monkeypatch.setattr(queue, "notify_owner_cycle_outcome", _send_then_record)
queue._deliver_pending_owner_report()
stored = evolution_lifecycle._read_evolution_campaign()
assert "pending_owner_report" not in stored
assert stored["active_transaction"]["commit_receipt"]["commit_sha"] == "5" * 40
def test_terminal_campaign_cannot_be_resurrected_by_a_stale_writer(tmp_path):
from supervisor import evolution_lifecycle
campaign, _ = _active_transaction(tmp_path)
stale = dict(campaign)
evolution_lifecycle.complete_evolution_campaign("owner stop", cleanup_worktree=False)
stale["status"] = "active"
assert evolution_lifecycle._write_evolution_campaign(stale) is False
assert evolution_lifecycle._read_evolution_campaign()["status"] == "stopped"
def test_stale_campaign_cannot_overwrite_a_new_campaign(tmp_path):
from supervisor import evolution_lifecycle, queue, state
state.init(tmp_path)
queue.init(tmp_path)
first = evolution_lifecycle.start_evolution_campaign("First", source="test")
stale = dict(first)
evolution_lifecycle.complete_evolution_campaign("done", cleanup_worktree=False)
second = evolution_lifecycle.start_evolution_campaign("Second", source="test")
stale["status"] = "active"
assert evolution_lifecycle._write_evolution_campaign(stale) is False
assert evolution_lifecycle._read_evolution_campaign()["id"] == second["id"]
def test_panic_campaign_close_uses_nonblocking_state_lock(tmp_path, monkeypatch):
from supervisor import evolution_lifecycle, queue, state
state.init(tmp_path)
queue.init(tmp_path)
evolution_lifecycle.start_evolution_campaign("Improve", source="test")
timeouts = []
monkeypatch.setattr(
state,
"acquire_file_lock",
lambda path, timeout_sec=4.0, **kw: timeouts.append(timeout_sec) or None,
)
evolution_lifecycle.complete_evolution_campaign(
"panic stop", status="stopped", cleanup_worktree=False,
)
assert timeouts == [0.001]
def test_unreadable_campaign_file_refuses_writes_instead_of_treating_it_as_absent(tmp_path, monkeypatch):
"""A campaign file that EXISTS but cannot be read (a transient Windows sharing
violation, corruption) is not "no campaign": the stale-write guard cannot compare
ids, so the write is refused — never allowed as if the slot were free. An absent
file stays writable (the first campaign of a fresh data root)."""
from supervisor import evolution_lifecycle, queue, state
state.init(tmp_path)
queue.init(tmp_path)
path = evolution_lifecycle._evolution_campaign_path()
assert not path.exists()
fresh = {"schema_version": 1, "id": "abc12345", "status": "active", "objective": "First"}
assert evolution_lifecycle._write_evolution_campaign(dict(fresh)) is True
assert path.is_file()
monkeypatch.setattr(evolution_lifecycle, "read_json_dict", lambda _p: None)
assert evolution_lifecycle._write_evolution_campaign({**fresh, "id": "other001"}) is False
monkeypatch.undo()
assert evolution_lifecycle._read_evolution_campaign()["id"] == "abc12345"