fix(hands,jobs): watchdog window reads the room tail, not the pending cursor
This commit is contained in:
1 parent
91bd50a66d
commit
47c31ac356
6 files changed
+341
-37
No files matched your search
@@ -12,7 +12,13 @@
|
||||
даёт честное `every=unknown`, а не подставленное число. Порог тревоги -
|
||||
`every * 1.5`, где `every` взят ИЗ СТРОКИ СОСЕДА, а не из своей константы.
|
||||
Каждый прогон, в котором показание снять нельзя (окно смотрит в прошлое,
|
||||
период неизвестен, отметок не видели ни разу), говорит это вслух в `ack`.
|
||||
окно не дочитано, период неизвестен, отметок не видели ни разу), говорит
|
||||
это вслух в `ack` и в лог: «в окне никого» и «окно не прочитано до конца» -
|
||||
это два РАЗНЫХ показания, и выглядеть одинаково они не должны.
|
||||
|
||||
Окно - последние `WINDOW` СООБЩЕНИЙ этой комнаты (`Vibegram.window`), а не
|
||||
«что нового с курсора»: курсор отдаёт строку соседа один раз, и следующий
|
||||
прогон на живой комнате увидел бы пустое окно.
|
||||
|
||||
Опора тревоги - `seen`, то есть когда Я увидел строку: `at=` внутри строки
|
||||
это показание соседа о себе, и остановившиеся у него часы не должны
|
||||
@@ -56,8 +62,8 @@ __all__ = [
|
||||
PEER = "claude-kai"
|
||||
"""Чей `hb` сторожим. Ник соседа стоит и в его строке, и в нашем `ack`."""
|
||||
WINDOW = 25
|
||||
"""Сколько событий просим у хаба явно: у `pending` свой дефолт (50), и молча
|
||||
опираться на чужой дефолт в стороже нельзя - размер окна едет в `ack`."""
|
||||
"""Сколько последних сообщений комнаты просим явно: молча опираться на чужой
|
||||
дефолт в стороже нельзя - размер окна едет в `ack`."""
|
||||
FACTOR = 1.5
|
||||
"""Порог молчания = заявленный соседом период × это. Одна потерянная строка
|
||||
не должна поднимать тревогу, две подряд - должны."""
|
||||
@@ -201,13 +207,20 @@ def ack_line(
|
||||
armed: bool,
|
||||
threshold: timedelta | None,
|
||||
fallback: bool,
|
||||
full: bool,
|
||||
) -> str:
|
||||
"""Расписка о прогоне в комнату: что видели, чем мерили и вооружены ли."""
|
||||
"""Расписка о прогоне в комнату: что видели, чем мерили и вооружены ли.
|
||||
|
||||
`win=` - про само окно: `25` это «последние 25 сообщений комнаты прочитаны
|
||||
целиком», `25/partial` - «дочитать историю не удалось», `stale` - «окно
|
||||
смотрит в прошлое». Пустое окно при `partial` ничего не доказывает.
|
||||
"""
|
||||
mark = span(threshold) + ("-fallback" if fallback and threshold else "")
|
||||
read = "stale" if stale else f"{window}" if full else f"{window}/partial"
|
||||
return (
|
||||
f"ack {peer} last={_iso(last) or ('never' if not armed else 'unknown')} "
|
||||
f"seen={_iso(seen) or 'never'} res={span(res)} "
|
||||
f"win={'stale' if stale else window} armed={'yes' if armed else 'no'} "
|
||||
f"win={read} armed={'yes' if armed else 'no'} "
|
||||
f"thr={mark}"
|
||||
)
|
||||
|
||||
@@ -276,6 +289,8 @@ class Report:
|
||||
|
||||
ack: str
|
||||
stale: bool
|
||||
full: bool
|
||||
"""Окно дочитано до конца истории комнаты: пустое окно - это показание."""
|
||||
armed: bool
|
||||
heard: bool
|
||||
"""Видели ли в окне хоть что-то от соседа - `hb` или его `ack`."""
|
||||
@@ -292,11 +307,16 @@ def look(
|
||||
*,
|
||||
now: datetime,
|
||||
res: timedelta | None,
|
||||
full: bool,
|
||||
window: int = WINDOW,
|
||||
peer: str = PEER,
|
||||
) -> Report:
|
||||
"""Разобрать окно, обновить состояние и решить, будить ли человека.
|
||||
|
||||
`events` - последние сообщения комнаты, `full` - дочитаны ли они до конца
|
||||
истории. Параметр без дефолта нарочно: «окно полное» - это показание, и
|
||||
подставлять его молча за того, кто окно читал, здесь нельзя.
|
||||
|
||||
Состояние меняется здесь, а `alerted` / `unarmed_told` ставит джоб - и
|
||||
только когда тревога действительно ушла: несостоявшаяся доставка не
|
||||
должна заглушать следующий прогон.
|
||||
@@ -320,7 +340,11 @@ def look(
|
||||
state.every_unknown = last.every is None
|
||||
if last.every is not None:
|
||||
state.every = last.every
|
||||
note = f"отметок соседа в окне: {len(beats)}"
|
||||
read = "история дочитана" if full else "история НЕ дочитана"
|
||||
note = (
|
||||
f"отметок соседа в окне: {len(beats)} "
|
||||
f"(сообщений комнаты: {len(events)} из {window}, {read})"
|
||||
)
|
||||
|
||||
armed = state.seen is not None
|
||||
threshold = state.every * FACTOR if state.every is not None else None
|
||||
@@ -334,6 +358,11 @@ def look(
|
||||
alarm: Literal["silence", "unarmed"] | None = None
|
||||
if stale:
|
||||
pass
|
||||
elif not full:
|
||||
# Хвост комнаты недочитан, а недостающее - как раз самое свежее:
|
||||
# пустое окно тут не доказывает молчания соседа, оно доказывает,
|
||||
# что мы не дочитали. Показания нет - тревоги нет.
|
||||
note += "; окно не дочитано - это не показание, тревога не берётся"
|
||||
elif not armed:
|
||||
if (
|
||||
not state.unarmed_told
|
||||
@@ -360,8 +389,10 @@ def look(
|
||||
armed=armed,
|
||||
threshold=threshold,
|
||||
fallback=state.every_unknown,
|
||||
full=full,
|
||||
),
|
||||
stale=stale,
|
||||
full=full,
|
||||
armed=armed,
|
||||
heard=heard,
|
||||
age=age,
|
||||
|
||||
Reference in new issue
Block a user