simbiot-harness/tests/test_followup.py
Simbiot Harness 2b266caab5 сжатие стора с подписью + «сначала ответ, потом работа» на мин-уровне
harness/compact.py + cli.py compact [--dry-run|--vacuum|--verify-archives]:
шум сек-уровня (CycleTicked sec, ReflexObserved без алерта/команд/каналов)
старше часа → архив jsonl.gz (0600, строки целиком со sig) → мост цепочки
(подписи унесённых строк, на которые ссылаются остающиеся, + маркеры
boundary:<id> для legacy-границ) → подписанный StoreCompacted с sha256
архива/строк/моста → удаление пачками. audit.verify_events принимает мост
только при совпадении хеша с подписанным событием; проекции подгружают
мост лениво, если хвост перекрыл сжатый диапазон (rebuild с нуля).
Репетиция на копии прода: 1389 → 200 МБ, verify 484 с/3.4 ГБ → 29 с/420 МБ,
архив 246 МБ — все 2.47 млн подписей проверены; rebuild_projections OK.
Найдено репетицией: legacy-граница (id 466633) и коллизия id сжатия в
одну секунду — оба покрыты тестом. scripts/compact-store.sh — процедура
с VACUUM под остановленными писателями.

мин-уровень: если до ответа нужна работа >30 с — сначала короткий
tg-reply, итог — cli.py tg-reply <id> --followup (в чат входящего:
группа → группа; идемпотентно по тексту). p90 «старт LLM → ответ» был 223 с.

hour: лимит 300 вызовов оставлен — реальный пик 205 (p90 169), 100
оборвал бы 8 из 19 тиков.
Тесты: test_compact 25 PASS, test_followup 10 PASS; полный набор — без регрессий.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:10:26 +00:00

73 lines
3.4 KiB
Python
Raw Permalink 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 -*-
"""tg-reply --followup: «сначала ответ — потом работа» (аудит 2026-10-16). Hermetic.
Запуск: ./venv/bin/python tests/test_followup.py
Проверяет: досылка уходит в ЧАТ входящего (группа → группа, личка →
личка), не в личку по умолчанию; работает после ReplySent (обычный
tg-reply уже запрещён); повтор с тем же текстом — SKIP (ledger), другой
текст — уходит; FollowupSent в сторе; неизвестный id — ValueError;
мандат мин-уровня содержит правило.
"""
import os
import sys
import tempfile
sys.path.insert(0, "/root/simbiot-harness")
os.environ["SIMBIOT_DB"] = os.path.join(tempfile.mkdtemp(prefix="simbiot_followup_"), "harness.db")
from harness import commands, es, tg_inbox
from harness.levels import LEVELS
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)
SENT = []
tg_inbox.send_message = lambda chat_id, text, reply_to=None, timeout=35: (
SENT.append((chat_id, text)) or {"ok": True})
tg_inbox._api = lambda *a, **k: {"ok": True}
for mid, chat in (("m-1", 7569223131), ("m-2", -1004402779881)):
es.append_event("inbox", mid, "MessageReceived", {"update_id": int(mid[2:]), "chat_id": chat,
"text": "сделай", "kind": "text"})
tg_inbox.send_reply("m-2", "понял, делаю")
check("первый ответ ушёл в группу", SENT[-1] == (-1004402779881, "понял, делаю"), SENT)
try:
tg_inbox.send_reply("m-2", "итог")
check("повторный tg-reply запрещён", False)
except ValueError:
check("повторный tg-reply запрещён", True)
def followup(mid, text):
import hashlib
aid = f"followup:{mid}:{hashlib.sha1(text.encode()).hexdigest()[:10]}"
return commands.deliver_artifact(aid, kind="message", owner="test", caption=text,
send=lambda: tg_inbox.send_followup(mid, text))
check("досылка после ответа — отправлена", followup("m-2", "готово: X") is True)
check("досылка — в чат входящего (группа)", SENT[-1] == (-1004402779881, "готово: X"), SENT[-1])
n = len(SENT)
check("повтор того же итога — SKIP", followup("m-2", "готово: X") is False and len(SENT) == n)
check("другой итог — уходит", followup("m-2", "ещё: Y") is True and len(SENT) == n + 1)
check("личка → личка", followup("m-1", "итог лички") and SENT[-1][0] == 7569223131)
evs = [e for e in es.get_events(0, 1000) if e["event_type"] == "FollowupSent"]
check("FollowupSent в сторе", len(evs) == 3, len(evs))
try:
tg_inbox.send_followup("m-404", "x")
check("неизвестный id — ValueError", False)
except ValueError:
check("неизвестный id — ValueError", True)
check("мандат мин-уровня: правило «сначала ответ»",
"СНАЧАЛА ОТВЕТ" in LEVELS["min"].mandate and "--followup" in LEVELS["min"].mandate)
print(f"\n{'OK' if not fails else 'FAILED'}: {len(fails)} fail(s)")
sys.exit(1 if fails else 0)