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>
73 lines
3.4 KiB
Python
73 lines
3.4 KiB
Python
# -*- 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)
|