mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
Accepted batch from the P1 review wave (3x SAFE): - drop the write-only _LedgerRowsMemo.records retention; the memo now holds only the final-rows dict + resume fingerprint (O(final rows)) - refuse to resume from a newline-less crash tail: _ledger_resume_state returns a non-resumable fingerprint (st_ino=-2) when the last byte is not a newline, so a later append that glues onto the unterminated line can never make a warm memo diverge from a fresh replay (regression test reproduces the probe; fails before the fix, passes after) - budget_remaining structural self-check: a passed projection is accepted only when its limit_usd equals the self-read limit; otherwise self-compute (closes the settings-hot-reload race) - unit pins for both de-triplication seams (api_state kwarg pass/withhold, budget_remaining use/fallback) - ARCH ledger paragraph precision: mtime consulted only at unchanged size, prefix integrity from append-only discipline, O(final rows) memory bound - thread-activity backfill marks its once-per-process guard only after a successful pass, so transient failures retry on the next reconcile tick (retry test added)
173 lines
6 KiB
Python
173 lines
6 KiB
Python
"""Tests for evolution/consciousness status snapshots."""
|
|
|
|
import json
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from starlette.applications import Starlette
|
|
from starlette.routing import Route
|
|
from starlette.testclient import TestClient
|
|
|
|
|
|
def test_evolution_status_waits_for_owner_chat(monkeypatch):
|
|
from supervisor import queue as queue_module
|
|
|
|
monkeypatch.setattr(queue_module, "PENDING", [])
|
|
monkeypatch.setattr(queue_module, "RUNNING", {})
|
|
monkeypatch.setattr(
|
|
queue_module,
|
|
"load_state",
|
|
lambda: {
|
|
"evolution_mode_enabled": True,
|
|
"owner_chat_id": None,
|
|
"evolution_cycle": 3,
|
|
"evolution_consecutive_failures": 0,
|
|
"last_evolution_task_at": "",
|
|
},
|
|
)
|
|
monkeypatch.setattr(queue_module, "budget_remaining", lambda st, **_kwargs: 25.0)
|
|
|
|
snapshot = queue_module.get_evolution_status_snapshot()
|
|
|
|
assert snapshot["status"] == "waiting_for_owner_chat"
|
|
assert snapshot["enabled"] is True
|
|
assert snapshot["owner_chat_bound"] is False
|
|
|
|
|
|
def test_evolution_status_reports_waiting_for_idle(monkeypatch):
|
|
from supervisor import queue as queue_module
|
|
|
|
monkeypatch.setattr(queue_module, "PENDING", [{"id": "task-1", "type": "task"}])
|
|
monkeypatch.setattr(queue_module, "RUNNING", {})
|
|
monkeypatch.setattr(
|
|
queue_module,
|
|
"load_state",
|
|
lambda: {
|
|
"evolution_mode_enabled": True,
|
|
"owner_chat_id": 7,
|
|
"evolution_cycle": 4,
|
|
"evolution_consecutive_failures": 0,
|
|
"last_evolution_task_at": "",
|
|
},
|
|
)
|
|
monkeypatch.setattr(queue_module, "budget_remaining", lambda st, **_kwargs: 25.0)
|
|
|
|
snapshot = queue_module.get_evolution_status_snapshot()
|
|
|
|
assert snapshot["status"] == "waiting_for_idle"
|
|
assert snapshot["pending_count"] == 1
|
|
|
|
|
|
def test_evolution_status_reports_budget_stop_when_disabled_after_run(monkeypatch):
|
|
from supervisor import queue as queue_module
|
|
|
|
monkeypatch.setattr(queue_module, "PENDING", [])
|
|
monkeypatch.setattr(queue_module, "RUNNING", {})
|
|
monkeypatch.setattr(
|
|
queue_module,
|
|
"load_state",
|
|
lambda: {
|
|
"evolution_mode_enabled": False,
|
|
"owner_chat_id": 7,
|
|
"evolution_cycle": 6,
|
|
"evolution_consecutive_failures": 0,
|
|
"last_evolution_task_at": "2026-03-31T10:00:00Z",
|
|
},
|
|
)
|
|
monkeypatch.setattr(queue_module, "budget_remaining", lambda st, **_kwargs: 1.25)
|
|
|
|
snapshot = queue_module.get_evolution_status_snapshot()
|
|
|
|
assert snapshot["status"] == "budget_stopped"
|
|
assert snapshot["budget_remaining_usd"] == 1.25
|
|
|
|
|
|
def test_consciousness_status_snapshot_exposes_runtime_fields():
|
|
from ouroboros.consciousness import BackgroundConsciousness
|
|
|
|
with patch.object(BackgroundConsciousness, "_build_registry", return_value=MagicMock()):
|
|
consciousness = BackgroundConsciousness(
|
|
drive_root=MagicMock(),
|
|
repo_dir=MagicMock(),
|
|
event_queue=None,
|
|
owner_chat_id_fn=lambda: 1,
|
|
)
|
|
|
|
consciousness.pause()
|
|
consciousness._next_wakeup_sec = 180
|
|
snapshot = consciousness.status_snapshot()
|
|
|
|
assert snapshot["paused"] is True
|
|
assert snapshot["next_wakeup_sec"] == 180
|
|
assert snapshot["last_idle_reason"] == "paused_by_active_task"
|
|
|
|
|
|
def test_evolution_data_strips_legacy_checkpoint_result_status(tmp_path, monkeypatch):
|
|
from ouroboros.evolution_checkpoints import CHECKPOINTS_REL
|
|
from ouroboros.gateway import control
|
|
|
|
repo = tmp_path / "repo"
|
|
repo.mkdir()
|
|
checkpoint_path = tmp_path / CHECKPOINTS_REL
|
|
checkpoint_path.parent.mkdir(parents=True, exist_ok=True)
|
|
checkpoint_path.write_text(
|
|
json.dumps({
|
|
"task_id": "evo-legacy",
|
|
"status": "completed",
|
|
"result_status": "failed",
|
|
"reason_code": "legacy_error",
|
|
"loop_outcome": {
|
|
"result_status": "failed",
|
|
"compat_result_status": "failed",
|
|
},
|
|
})
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
async def fake_collect_metrics(*_args, **_kwargs):
|
|
return []
|
|
|
|
monkeypatch.setattr("ouroboros.utils.collect_evolution_metrics", fake_collect_metrics)
|
|
control._evo_cache.clear()
|
|
control._evo_task = None
|
|
app = Starlette(routes=[Route("/api/evolution-data", endpoint=control.api_evolution_data)])
|
|
app.state.drive_root = tmp_path
|
|
app.state.repo_dir = repo
|
|
|
|
payload = TestClient(app).get("/api/evolution-data?force=1").json()
|
|
|
|
checkpoint = payload["checkpoints"][0]
|
|
assert "result_status" not in checkpoint
|
|
assert "result_status" not in checkpoint["loop_outcome"]
|
|
assert "compat_result_status" not in checkpoint["loop_outcome"]
|
|
assert checkpoint["outcome_axes"]["execution"]["status"] == "failed"
|
|
|
|
|
|
def test_budget_remaining_uses_valid_projection_and_self_computes_on_limit_mismatch(monkeypatch):
|
|
"""De-triplication seam pin: a passed projection is used verbatim (zero
|
|
ledger reads) ONLY when its limit_usd equals the limit budget_remaining
|
|
reads itself; a mismatched limit (settings hot-reload race, wrong-limit
|
|
caller) falls through to self-computation."""
|
|
from ouroboros import usage_accounting as ua
|
|
from supervisor import state
|
|
|
|
monkeypatch.setattr(state, "TOTAL_BUDGET_LIMIT", 10.0)
|
|
calls = {"projection": 0, "import": 0}
|
|
|
|
def _projection(_root, **_kwargs):
|
|
calls["projection"] += 1
|
|
return {"limit_usd": 10.0, "remaining_known_usd": 3.75}
|
|
|
|
monkeypatch.setattr(ua, "usage_projection", _projection)
|
|
monkeypatch.setattr(
|
|
ua, "ensure_legacy_imported",
|
|
lambda _root: calls.__setitem__("import", calls["import"] + 1),
|
|
)
|
|
|
|
valid = {"limit_usd": 10.0, "remaining_known_usd": 4.25}
|
|
assert state.budget_remaining({}, projection=valid) == 4.25
|
|
assert calls == {"projection": 0, "import": 0}
|
|
|
|
stale = {"limit_usd": 5.0, "remaining_known_usd": 4.25}
|
|
assert state.budget_remaining({}, projection=stale) == 3.75
|
|
assert calls == {"projection": 1, "import": 1}
|