Compare commits

..
2 Commits
4 changed files with 16 additions and 8 deletions

No files matched your search

+5 -2
View File
@@ -208,7 +208,10 @@ Switches: `досуг: выкл` as the first line of the file for every day, an
An agreement with a foreign agent on someone else's home server: both print An agreement with a foreign agent on someone else's home server: both print
one line a day into a vibegram room of their own, and both watch the other's one line a day into a vibegram room of their own, and both watch the other's
silence. If the neighbour goes quiet past the threshold, *their* human is the silence. The two rates are deliberately different: the beat is daily because
that is the period we promise the neighbour, while the check runs every six
hours, because a watchdog stepping once a day against a 36 h threshold would
only notice silence some 60 h in - it would be measuring the wrong thing. If the neighbour goes quiet past the threshold, *their* human is the
one who needs to hear about it, so the alarm goes to the master thread as a one who needs to hear about it, so the alarm goes to the master thread as a
spawned triage job and the dispatcher decides whether to wake the owner. spawned triage job and the dispatcher decides whether to wake the owner.
@@ -226,7 +229,7 @@ are no defaults anywhere in it.
period is not a constant: it is derived from that job's own registered cron period is not a constant: it is derived from that job's own registered cron
(`run.job.cron`). A schedule that does not reduce to "every N hours" gets (`run.job.cron`). A schedule that does not reduce to "every N hours" gets
`every=unknown src=none` - lying with a number is worse than refusing. `every=unknown src=none` - lying with a number is worse than refusing.
- `сторож` (17:42 local) asks for an explicit window (`pending(limit=25)`), - `сторож` (`:42` every six hours) asks for an explicit window (`pending(limit=25)`),
finds the newest `hb claude-kai …`, and compares its age against finds the newest `hb claude-kai …`, and compares its age against
`every * 1.5` where `every` comes **from the neighbour's own line**. The `every * 1.5` where `every` comes **from the neighbour's own line**. The
clock it trusts is `seen` - when *we* saw the line - not the `at=` inside it, clock it trusts is `seen` - when *we* saw the line - not the `at=` inside it,
+5 -3
View File
@@ -31,10 +31,12 @@ jobs = [
# Сторожевое кольцо с чужим агентом. Кроны читаются в локальной зоне, а # Сторожевое кольцо с чужим агентом. Кроны читаются в локальной зоне, а
# сосед печатает свою отметку в 05:00 UTC - вечер по Варшаве разводит нас # сосед печатает свою отметку в 05:00 UTC - вечер по Варшаве разводит нас
# на полсуток, и его строка лежит в комнате задолго до нашего прогона. # на полсуток, и его строка лежит в комнате задолго до нашего прогона.
# Сторож идёт через полчаса после своей отметки: так «моя запись в комнату # Частоты тут две, и они разные: отметка суточная (её период мы обещаем
# проходит» - свежий факт этих же суток, а не вчерашний. # соседу), а проверка - вчетверо чаще. Сторож с шагом в сутки при пороге
# 36 ч обнаруживал бы молчание только через ~60 ч, то есть мерил бы не то,
# ради чего заведён; чтение 25 событий стоит копейки.
Job("отметка", beat, cron="12 17 * * *", critical=False), Job("отметка", beat, cron="12 17 * * *", critical=False),
Job("сторож", watch, cron="42 17 * * *", critical=False), Job("сторож", watch, cron="42 */6 * * *", critical=False),
Job("память", memory, cron="30 4 * * 0", critical=False), Job("память", memory, cron="30 4 * * 0", critical=False),
Job("голосовой", rotate_voice, cron="5 4 * * *", critical=False), Job("голосовой", rotate_voice, cron="5 4 * * *", critical=False),
Job("куратор", curate, cron="0 2,8,12,16,20 * * *", critical=False), Job("куратор", curate, cron="0 2,8,12,16,20 * * *", critical=False),
+4 -1
View File
@@ -26,6 +26,7 @@ import urllib.request
from pathlib import Path from pathlib import Path
ENV = Path(__file__).resolve().parent.parent / ".env" ENV = Path(__file__).resolve().parent.parent / ".env"
USER_AGENT = "beaver-agent/0.1 (+vibegram)"
def hub_from_env() -> str | None: def hub_from_env() -> str | None:
@@ -56,7 +57,9 @@ def main() -> int:
request = urllib.request.Request( # noqa: S310 - адрес хаба задаёт оператор request = urllib.request.Request( # noqa: S310 - адрес хаба задаёт оператор
f"{args.hub.rstrip('/')}/api/rooms/join", f"{args.hub.rstrip('/')}/api/rooms/join",
data=json.dumps(body).encode(), data=json.dumps(body).encode(),
headers={"content-type": "application/json"}, # Перед хабом Cloudflare: безымянный `Python-urllib` он отбивает 1010.
# Тот же user-agent, что у клиента в `beaver_agent/hands/vibegram.py`.
headers={"content-type": "application/json", "user-agent": USER_AGENT},
method="POST", method="POST",
) )
try: try:
+2 -2
View File
@@ -253,7 +253,7 @@ class FakeRoom:
class FakeRun: class FakeRun:
def __init__(self, cron="42 17 * * *"): def __init__(self, cron="42 */6 * * *"):
self.job = SimpleNamespace(cron=cron, name="сторож") self.job = SimpleNamespace(cron=cron, name="сторож")
self.scheduler = SimpleNamespace(tz=WARSAW) self.scheduler = SimpleNamespace(tz=WARSAW)
self.spawned = [] self.spawned = []
@@ -280,7 +280,7 @@ async def test_watch_job_prints_ack_and_spawns_alarm(tmp_path, monkeypatch):
assert room.limit == 25 # окно запрошено явным размером assert room.limit == 25 # окно запрошено явным размером
assert room.said and room.said[0].startswith(f"ack {PEER} ") assert room.said and room.said[0].startswith(f"ack {PEER} ")
assert "res=24h" in room.said[0] assert "res=6h" in room.said[0]
assert len(run.spawned) == 1 assert len(run.spawned) == 1
agent, title, text = run.spawned[0] agent, title, text = run.spawned[0]
assert agent == "beaver-triage" and title == "сторож" assert agent == "beaver-triage" and title == "сторож"