mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
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>
170 lines
6.4 KiB
Python
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}
|