fix(updater): кнопка обновления ставила откат программы назад

Сравнение сборок шло только на равенство коммитов: любое расхождение
объявлялось обновлением. Последний релиз на GitHub указывал на 380c218
от 30 августа, у владельца стояла сборка новее — хаб предложил «обновиться»
на старый коммит, владелец нажал и получил откат.

Добавлена проверка по времени: если релиз опубликован раньше, чем
установлена текущая сборка (deployed_at из deployment_manifest.json),
это откат, а не обновление. Такой релиз не предлагается, и владельцу
пишется, что установлена сборка новее опубликованного релиза.

Проверено на фактических данных владельца: релиз 380c218 от 2026-08-30
против установки от 2026-08-31 даёт update_available=False.

Версия остаётся 0.1.1 намеренно, сборки различаются коммитом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-31 19:17:57 +07:00
parent 5c2a0692d3
commit b27c2b6e84

View file

@ -43,6 +43,45 @@ ALLOWED_UPDATE_HOSTS = {
}
def get_installed_build_time() -> str:
"""Время установки текущей сборки из deployment_manifest.json (поле deployed_at).
Нужно, чтобы отличать более новый релиз от более старого. Сравнение
коммитов на равенство этого не умеет: любое расхождение оно объявляет
обновлением, включая коммит-предок. Владелец нажимал «Обновить» и
получал откат программы назад.
"""
for mf in (
paths.get_repo_root() / "deployment_manifest.json",
paths.get_hermes_home() / "plugins" / "antigravity-provider" / "deployment_manifest.json",
):
try:
if mf.is_file():
data = json.loads(mf.read_text(encoding="utf-8-sig"))
val = str(data.get("deployed_at") or "").strip()
if val:
return val
except Exception as exc:
logger.debug("Не удалось прочитать %s: %s", mf, exc)
return ""
def _is_release_older(published_at: str, installed_at: str) -> bool:
"""True, если релиз опубликован раньше, чем установлена текущая сборка."""
if not published_at or not installed_at:
return False
try:
from datetime import datetime
def _parse(v: str):
return datetime.fromisoformat(v.strip().replace("Z", "+00:00"))
return _parse(published_at) < _parse(installed_at)
except Exception as exc:
logger.debug("Не удалось сравнить даты %r и %r: %s", published_at, installed_at, exc)
return False
def get_installed_commit() -> str:
"""Return currently installed git commit hash, checking deployment manifest, env, and git."""
# 1. Environment variable override
@ -389,6 +428,14 @@ class UpdateManager:
if inst_clean == lat_clean or inst_clean.startswith(lat_clean) or lat_clean.startswith(inst_clean):
update_available = False
message = "Установлена последняя сборка"
elif _is_release_older(published_at, get_installed_build_time()):
# Коммиты разные, но релиз старше установленной сборки —
# это откат назад, а не обновление.
update_available = False
message = (
f"Установлена сборка новее опубликованного релиза "
f"({lat_clean[:7]} от {published_at[:10]})"
)
else:
update_available = True
message = f"Доступно обновление (сборка {lat_clean[:7]})"