115 lines
No EOL
6.1 KiB
Python
115 lines
No EOL
6.1 KiB
Python
# -*- 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) |