ouroboros/tests/test_evolution_status.py
Ouroboros c7cd252f2a Pin the alarm clock, the wake message and retarget the retired-loop harnesses
tests/test_consciousness.py is rewritten for tick(now): every branch
(disabled, live wake, live owner turn, not due, allowance unknown/exhausted,
no owner chat, launch with the envelope and rendered message, refusal,
finish -> chosen interval / pending reason / doubling backoff, notify
debounce, the same-instant admission race, self-trigger exclusion, boot floor,
the one-time inbox archive, graceful stop of a live wake, the snapshot keys).
tests/test_consciousness_wake.py pins the renderer (placeholders, event
truncation, fail-soft readers), the template's hints and the server status
projection.

Retired with the loop: test_consciousness_observations.py,
test_observation_fold.py, test_background_model_wait_browser.py, the
background twins in test_openai_chat_dispatch*.py, test_anthropic_native_custody.py,
test_transport_death_retry.py, the background-role pins in
test_owner_live_delivery.py / test_quiz_answer.py, the BG budget pins and the
consciousness max_tokens row. The context harnesses (test_repo_read_limits,
test_context, test_durable_learning_completeness) now assert the same
governance and gap facts on the wake's Main context through build_llm_messages;
test_background_model_wait.py keeps only the generic model-wait facts. The
docs-sync identifier audit resolves the wake template against the full
registry plus its render placeholders; the people/identity prompt pins follow
the new wording; test_settings_honesty pins that the wake-up bounds apply
without a restart; the persistence scan and the smoke tool inventory absorb
the retired fold segments and the registered set_next_wakeup.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 06:03:21 +03:00

170 lines
6.4 KiB
Python

"""Tests for evolution/consciousness status snapshots."""
import json
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_the_alarm_facts(monkeypatch, tmp_path):
from ouroboros import consciousness as clock_module
from ouroboros.consciousness import BackgroundConsciousness
from supervisor import state
monkeypatch.setattr(state, "load_state", lambda: {"bg_consciousness_enabled": True, "owner_chat_id": 1})
monkeypatch.setattr(clock_module, "allowance_window", lambda root, now=None: {
"status": "available", "limit_usd": 20.0, "accounted_usd": 3.0, "remaining_usd": 17.0, "resets_at": ""})
monkeypatch.setattr(BackgroundConsciousness, "_running_roots", staticmethod(lambda: 0))
clock = BackgroundConsciousness(tmp_path, tmp_path / "repo", lambda: 1, now=1_800_000_000.0)
clock.notify("task_finished:t1:completed")
snapshot = clock.status_snapshot()
assert snapshot["enabled"] is True and snapshot["pending_reason"] == "task_finished:t1:completed"
assert snapshot["next_wake_at"] and snapshot["last_wake_at"] == "" and snapshot["live_wake_task_id"] == ""
assert snapshot["spent_24h_usd"] == 3.0 and snapshot["daily_usd"] == 20.0
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}