simbiot-harness/tests/test_budget_stub_guard.py

115 lines
No EOL
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""Guard бюджет-стабов (e-261001074527-f0c1b4, hermetic, без LLM/TG).
Запуск: ./venv/bin/python tests/test_budget_stub_guard.py
Ночной тик 20727 пачкой закрыл 5 L-задач отпиской воркера «бюджет исчерпан».
Гвард: complete_task_with_delivery со стоп-фразой → ValueError + авто-requeue
в ready (TaskPromoted), TaskCompleted НЕ пишется. Честные результаты со
встречным упоминанием бюджета гвард не трогает.
Проверяет: looks_like_budget_stub на ночных текстах (стоп-фраза воркера,
end_turn-сообщение tool-budget, шаблон ночной доставки) — true; честные
результаты («уточнил бюджет», «бюджет рекламной кампании исчерпан») — false;
complete со стабом → ValueError + задача вернулась в ready; повторный
complete с реальным результатом → done. CLI requeue по guard-сообщению
работает (задача не теряется).
"""
import os
import sys
_DST = "/tmp/simbiot-test-guard.db"
for p in (_DST, _DST + "-wal", _DST + "-shm"):
if os.path.exists(p):
os.unlink(p)
os.environ["SIMBIOT_DB"] = _DST
sys.path.insert(0, "/root/simbiot-harness")
from harness import es
from harness import commands
es.init_db()
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
if not cond:
fails.append(name)
def task_state(tid):
model = es.run_projections()
t = model["tasks"].get(tid)
return t["status"] if t else None
def task_events(tid):
db = es._conn()
try:
rows = db.execute("SELECT event_type FROM events WHERE aggregate_id=?", (tid,)).fetchall()
return [r["event_type"] for r in rows]
finally:
db.close()
# ── 1. Predicate: ночные шаблоны распознаются ──
nightly = [
"✅ Готово, тик завершён: бюджет исчерпан",
"тик завершён: бюджет шагов исчерпан (30 tool-вызовов). Выше — результат того, что успело выполниться.",
"тиковый бюджет шагов исчерпан (30 tool-вызовов): вызов отменён; новые вызовы инструментов отключены до конца тика",
]
for i, txt in enumerate(nightly):
check(f"1.{i}: stub распознан", commands.looks_like_budget_stub(txt), txt[:50])
# ── 2. Predicate: честные результаты не трогаем ──
honest = [
"Утверждён оффер: 10 мест × $99.9/мес. Задача закрыта, счётчик на лендинг — следующая задача.",
"Проверил рекламный кабинет: бюджет рекламной кампании исчерпан, переключил на резервный.",
"Проанализировал траты за месяц; бюджет месяца исчерпан частично — отчёт готов.",
"Рефакторинг завершён из 3 шагов. Всё влезло, бюджет не трогали.",
]
for i, txt in enumerate(honest):
check(f"2.{i}: честный не стаб", not commands.looks_like_budget_stub(txt), txt[:50])
# ── 3. Живая задача: complete со стабом → ValueError + requeue в ready ──
tid, _sz = commands.submit_task(title="Тест guard", minutes=30,
details="тестовая задача гвирда бюджет-стабов")
es.append_event("task", tid, "TaskClaimed", {"level": "hour", "session_id": "t-guard"})
es.run_projections()
check("3.0: задача running", task_state(tid) == "running")
stub = "тик завершён: бюджет исчерпан"
try:
commands.complete_task_with_delivery(tid, stub, level="hour")
check("3.1: complete со стабом отклонён", False, "нет ValueError")
except ValueError as e:
check("3.1: complete со стабом отклонён", "ПЕРЕВЕД" in str(e).upper() or "ready" in str(e), str(e)[:80])
check("3.2: после guard — задача в ready", task_state(tid) == "ready", task_state(tid))
check("3.3: TaskCompleted НЕ записан", "TaskCompleted" not in task_events(tid), task_events(tid))
check("3.4: requeue-событие есть", "TaskPromoted" in task_events(tid))
# ── 4. Тот же тик: повторный complete с реальным результатом → done ──
es.append_event("task", tid, "TaskClaimed", {"level": "hour", "session_id": "t-guard2"})
tid2, delivered = commands.complete_task_with_delivery(
tid, "Реальный результат: расчёт суммы бюджета проекта завершён", level="hour")
check("4.0: честный complete после requeue", task_state(tid) == "done", task_state(tid))
check("4.1: TaskCompleted записан", task_events(tid).count("TaskCompleted") == 1)
# ── 5. Честный «бюджет … исчерпан» в реальном результате → TaskCompleted ок ──
tid3, _sz3 = commands.submit_task(title="Тест честного", minutes=30,
details="тест: результат содержит бюджет-фразу по делу")
es.append_event("task", tid3, "TaskClaimed", {"level": "hour", "session_id": "t-guard3"})
try:
commands.complete_task_with_delivery(
tid3, "Проверил кабинет: бюджет рекламной кампании исчерпан, отчёт готов",
level="hour")
check("5.0: честный результат проходит", task_state(tid3) == "done", task_state(tid3))
except ValueError as e:
check("5.0: честный результат проходит", False, str(e)[:80])
print()
print("FAILS:", len(fails))
sys.exit(1 if fails else 0)