mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
Some checks failed
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · macos-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · ubuntu-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / fixture · windows-latest · exact managed runtime, fake harness, no model (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Has been cancelled
Submit Claudexor dependency / Submit managed runtime snapshot (push) Has been cancelled
CI / quick-test (push) Has been cancelled
CI / benchmark-methodology (push) Has been cancelled
CI / full-test (macos-latest) (push) Has been cancelled
CI / full-test (ubuntu-latest) (push) Has been cancelled
CI / full-test (windows-latest) (push) Has been cancelled
CI / betterleaks-platform-smoke (macos-latest) (push) Has been cancelled
CI / betterleaks-platform-smoke (ubuntu-latest) (push) Has been cancelled
CI / betterleaks-platform-smoke (windows-latest) (push) Has been cancelled
CI / integration-test (push) Has been cancelled
CI / skill-smoke (macos-latest) (push) Has been cancelled
CI / skill-smoke (ubuntu-latest) (push) Has been cancelled
CI / skill-smoke (windows-latest) (push) Has been cancelled
CI / marker-guards (push) Has been cancelled
CI / ui-smoke (push) Has been cancelled
CI / docker-ui-smoke (push) Has been cancelled
CI / docker-portable-test (push) Has been cancelled
CI / system-e2e-mock (push) Has been cancelled
CI / e2e-live (SM1 x1 — largest subset feasible under the $30 cap) (push) Has been cancelled
Scorecard analysis workflow / Scorecard analysis (push) Has been cancelled
Sync Joi Lab Fork Mirror / sync (push) Has been cancelled
CI / release-preflight (push) Has been cancelled
CI / build (dmg, macos-latest, macos-arm64, syft_1.50.0_darwin_arm64.tar.gz, syft, e32fdb9d47823fa633748a1efca2528fd77c37469ea93c9e40ab835da44e4cce) (push) Has been cancelled
CI / build (tar.gz, ubuntu-latest, linux-x86_64, syft_1.50.0_linux_amd64.tar.gz, syft, bf7b29ff57f06da30918266a0e1c2885a8f99784798d1bdb1628886aa015d788) (push) Has been cancelled
CI / build (zip, windows-latest, windows-x64, syft_1.50.0_windows_amd64.zip, syft.exe, 815ee6973ec5dff6a671d7f41b0e78835a8c45b91d5a39f4743ea1cee833d3be) (push) Has been cancelled
CI / vendor-package-smoke (push) Has been cancelled
CI / release (push) Has been cancelled
Preserve real lock contention, the exact receipt and lost-update assertions while removing a scheduler-dependent timeout from test setup. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
376 lines
15 KiB
Python
376 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.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()
|
|
result = {}
|
|
|
|
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 done.wait(0.1) is False
|
|
finally:
|
|
release_exclusive_file_lock(lock_path, lock_fd)
|
|
assert done.wait(2.0) is True
|
|
thread.join(timeout=1.0)
|
|
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"
|