From 3736fb3948eb5d367613059216c35c01243228d8 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 14 Oct 2026 12:40:46 +0400 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B0=D1=83=D0=B7=D0=B0=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B6=D1=83=D1=80?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D0=B0=20=D0=B2=20=D0=B1=D1=83=D0=B4=D1=83?= =?UTF-8?q?=D1=89=D0=B5=D0=BC,=20claim=20=D0=B1=D0=B5=D0=B7=20=D0=BE=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D0=BA=D0=B8=20=D0=B8=D0=B7=20=D0=B6=D1=83=D1=80?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D0=B0=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M-6, M-7 (agent#12). Co-Authored-By: Claude Opus 5.5 --- src/agent.py | 12 +++++++----- tests/fake_hub.py | 2 ++ tests/test_runner.py | 15 ++++++++++++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/agent.py b/src/agent.py index c90bd55..ed2dc16 100644 --- a/src/agent.py +++ b/src/agent.py @@ -940,7 +940,9 @@ class Runner: def restore_pace(self) -> None: """После перезапуска время последнего вызова Jira неизвестно (живёт в памяти). Берём самое позднее время записи - журнала: finished пишется сразу после последнего вызова команды, оценка не раньше настоящего вызова (M-9).""" + журнала: finished пишется сразу после последнего вызова команды, оценка не раньше настоящего вызова (M-9). + Время в будущем (часы ушли назад) — считаем «сейчас»: пауза полная (M-6 #12). Оценка служит только паузе: + last_call_iso (уходит хабу в claim) ставит лишь настоящий вызов Jira (M-7 #12).""" if self.jira.last_call_at is not None: return at = self.journal.last_at() @@ -948,11 +950,11 @@ class Runner: return ago = (datetime.now(timezone.utc) - at).total_seconds() if ago < 0: - log("журнал: время последней записи в будущем (%s) — темп не восстанавливаю" % at.isoformat()) - return + log("журнал: время последней записи в будущем (%s) — пауза перед первой командой полная" % at.isoformat()) + ago = 0 self.jira.last_call_at = time.monotonic() - ago - self.jira.last_call_iso = at.astimezone(timezone.utc).replace(microsecond=0).isoformat() - log("темп: последний вызов Jira не позже %s (по журналу)" % self.jira.last_call_iso) + log("темп: последний вызов Jira не позже %s (по журналу)" + % at.astimezone(timezone.utc).replace(microsecond=0).isoformat()) def send_result(self, rec: dict) -> bool: """Отчёт хабу; повторяется каждые 10 с, пока хаб не подтвердит. 404/409/422 — хаб отверг (команда отменена diff --git a/tests/fake_hub.py b/tests/fake_hub.py index abad0af..0b5b701 100644 --- a/tests/fake_hub.py +++ b/tests/fake_hub.py @@ -16,6 +16,7 @@ class FakeHub: self.states = [] self.known = [] self.claims = 0 + self.claim_bodies = [] self.fail_results = 0 self.reject_results = {} self.in_flight = None @@ -68,6 +69,7 @@ class FakeHub: def route(self, method, path, body): if path == "/agent/claim": + self.claim_bodies.append(body or {}) self.claims += 1 if self.in_flight and self.commands[self.in_flight]["status"] == "claimed": return 200, {"command": None, "settings_version": self.settings["settings_version"]} diff --git a/tests/test_runner.py b/tests/test_runner.py index a6e8f5f..9d06504 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -60,14 +60,23 @@ class RunnerTest(unittest.TestCase): self.runner.loop() self.assertAlmostEqual(sum(self.sleeps), 20, delta=1.5) - def test_future_journal_time_ignored(self): - at = (datetime.now(timezone.utc) + timedelta(hours=1)).replace(microsecond=0).isoformat() + def test_future_journal_time_means_full_pause(self): + at = (datetime.now(timezone.utc) + timedelta(hours=2)).replace(microsecond=0).isoformat() self.journal.append({"kind": "acked", "command_id": 99, "at": at}) self.hub.enqueue("ensure_fields", {"key": "AURORA-1", "duedate": "2026-10-23"}, pause=30) self.runner.loop() - self.assertEqual(self.sleeps, []) # время из будущего — как пустой журнал + self.assertAlmostEqual(sum(self.sleeps), 30, delta=1.5) # часы ушли назад — пауза полная (M-6 #12) self.assertEqual(self.hub.results[1]["status"], "done") + def test_claim_after_restart_sends_no_journal_estimate(self): + at = (datetime.now(timezone.utc) - timedelta(seconds=10)).replace(microsecond=0).isoformat() + self.journal.append({"kind": "acked", "command_id": 99, "at": at}) + self.hub.enqueue("ensure_fields", {"key": "AURORA-1", "duedate": "2026-10-23"}) + self.runner.loop() + self.assertIsNone(self.hub.claim_bodies[0].get("last_jira_call_at")) # оценка журнала — только для паузы (M-7 #12) + self.assertIsNotNone(self.hub.claim_bodies[-1].get("last_jira_call_at")) # после вызова Jira — настоящее время + self.assertIsNotNone(self.runner.jira.last_call_at) + def test_restore_pace_keeps_existing_last_call(self): at = (datetime.now(timezone.utc) - timedelta(seconds=100)).replace(microsecond=0).isoformat() self.journal.append({"kind": "acked", "command_id": 99, "at": at})