docs(agents): задание A27 — обновление из самой программы

Владелец обновляется вручную на трёх машинах. Механизм обновления в проекте
есть, но мёртв в каждом звене, и это проверено: веб-клиент check_updates не
вызывает вовсе (0 вхождений); DEFAULT_UPDATE_URL смотрит на заброшенный
второй репозиторий, чей манифест застыл на 0.1.1 от 21 августа; версия
захардкожена в 0.1.1 и подниматься не должна, поэтому сравнение по semver
никогда не скажет «есть обновление».

Рабочая часть — apply_update_sync с резервной копией, py_compile-проверкой и
откатом — сохраняется, переписывать её не нужно.

В задании принято решение, которое агентам не следует угадывать: признак
новизны — коммит, а не версия, источник — релизы основного репозитория, куда
поставка идёт на самом деле.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-25 17:39:26 +07:00
parent a1e1db75bf
commit 7b7b527103

View file

@ -0,0 +1,171 @@
# Задание A27: обновление из самой программы
## Дата поступления
2026-08-25
## База
Проверочный HEAD на момент выдачи: **`a1e1db7`**.
## Ветка
`antigravity/in-app-updates`
## Порядок исполнения
Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-5** написан для аудитора.
---
## Порядок работы с git
```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/in-app-updates
git commit -m "..." <- сначала коммит
git push -u origin antigravity/in-app-updates
```
В `main` напрямую не пушить. В конце — push и проверка `git log --oneline -1`, `git status` чистый.
---
## Задача
Владелец: «а у нас реализовано обновление с программы? чтобы когда выходит новый ревью, в программе появлялось обновить? если нет, надо сделать».
Сейчас обновление ставится вручную: скачать установщик с релиза и запустить. На двух машинах и сервере это делается по несколько раз в день.
---
## Состояние на сегодня — проверено исполнением, заново не выясняйте
Механизм **существует**, но не работает ни в одном звене. Четыре причины, каждая подтверждена:
**1. Веб-интерфейс его не вызывает вообще.**
```
grep -c "check_updates" router/web/static/app.js -> 0
grep -c "check_updates" router/web/static/index.html -> 0
```
Кнопка была только в десктопе, который выводится из обращения.
**2. Источник обновлений — заброшенный второй репозиторий.**
`DEFAULT_UPDATE_URL` (`updater/update_manager.py:77`) указывает на
`ochenstarik-ui/hermes-hub-releases`. Репозиторий существует, манифест отдаёт `200`, но его содержимое:
```
version: 0.1.1
published_at: 2026-08-21T09:56:00Z
package_url: .../releases/download/v0.1.1/hermes-hub-0.1.1.zip
```
Это состояние **до** всей работы последних дней. Настоящая поставка давно идёт через релизы основного репозитория `ochenstarik-ui/hermes-hub` (тег `build-2026.08.25`), о которых обновлятор не знает.
**3. Версия не меняется и меняться не должна.**
`version.py:4``__version__ = "0.1.1"`, и в заданиях прямо запрещено создавать тег `v0.1.1`. Сборки в проекте различаются **коммитом**, а не semver: именно поэтому в `deployment_manifest.json` пишется `git_commit`. Сравнение версий в текущем виде **никогда** не скажет «есть обновление», даже если манифест обновить.
**4. Механизм применения при этом рабочий и его не надо переписывать.**
`UpdateManager.apply_update_sync` делает резервную копию `src/assets/config/launcher`, распаковывает пакет, проверяет каждый `.py` через `py_compile`, прогоняет дымовой импорт и **откатывается при любой ошибке**. Это ценная часть, сохраните её.
`paths.get_repo_root()` в установленной раскладке возвращает
`~/.hermes/plugins/antigravity-provider` (там есть `assets`), то есть цель применения верная.
---
## Решение, которое принято за вас
Чтобы не гадать: **признак новизны — коммит, а не версия.**
Источник — **релизы основного репозитория** `ochenstarik-ui/hermes-hub`. Второй репозиторий `hermes-hub-releases` из обращения выводится; трогать его не нужно, просто перестаньте на него смотреть.
Установленный коммит уже записан в `deployment_manifest.json` (`git_commit`), сборщики его туда кладут — и виндовый, и линуксовый через `BUILD_COMMIT`. Опубликованный коммит указан в заголовке и в описании релиза.
Если для сравнения удобнее отдельное поле — заведите его в описании релиза или в манифесте, но **выводите из реального коммита сборки**, а не подставляйте руками.
## P0-1. Определение «есть обновление»
Переписать `check_for_updates` на новый источник.
1. Опрашивается GitHub API релизов основного репозитория. Репозиторий публичный, токен не нужен; на анонимные запросы есть ограничение по частоте — учтите и не опрашивайте чаще, чем нужно.
2. Сравнивается **установленный коммит** с коммитом последнего релиза.
3. Совпали — «установлена последняя сборка». Разошлись — «доступно обновление» с датой релиза и описанием.
4. **Сеть недоступна — так и сказать.** Не «обновлений нет»: это разные утверждения, и путать их нельзя. Прежний код при `404` возвращал `update_available=False`, то есть отказ выглядел как «всё актуально».
Список разрешённых хостов (`ALLOWED_UPDATE_HOSTS`) сохраните и дополните, а не убирайте: качать код с произвольного адреса нельзя.
## P0-2. Кнопка в веб-интерфейсе
Сейчас её нет. Требуется:
1. **Тихая проверка при запуске** и далее по расписанию. Интервал — настройка, не константа.
2. Когда обновление есть — заметная, но не навязчивая отметка в шапке рядом с версией. Не модальное окно поверх работы.
3. По нажатию — что за сборка, когда опубликована, что изменилось, и кнопка установки.
4. Пока обновления нет — показывать установленную сборку и время последней проверки. Это и есть ответ на вопрос «а у меня свежее?».
Действие `check_updates` уже есть в `action_handler` (`action_handler.py:582`), второй реализации не заводить. Появятся новые — впишите в `docs/web-api/CONTRACT.md`.
## P0-3. Установка обновления на обеих платформах
Владелец работает на Windows и на Linux-сервере, где интерфейс открыт по сети. Обе поддерживаются.
Простой и честный путь: скачать **тот самый установщик из релиза**, который владелец сейчас качает руками, проверить контрольную сумму и запустить. Не изобретайте второй способ доставки — он немедленно разойдётся с первым.
Обязательно:
- **Проверка контрольной суммы до запуска.** Суммы публикуются в `checksums.txt` рядом с установщиками.
- **Сервер перезапускается сам** после установки, иначе владелец останется со старым процессом и решит, что обновление не сработало. На Linux запуск идёт через `~/.local/bin/hermes-hub-web`.
- **Откат при неудаче.** Механизм в `apply_update_sync` уже есть — используйте его, а не пишите заново.
- **Учётные данные и настройки не трогать.** `agy_profiles/`, `hub_settings.json`, `router_profiles.yaml` обязаны пережить обновление. Установщик это умеет («Preserving existing user router_profiles.yaml»), но проверьте отдельно и напишите в отчёте.
Если установку на какой-то платформе решите не автоматизировать — **так и напишите**, и покажите в интерфейсе готовую команду вместо кнопки. Молчаливо неработающая кнопка хуже честной команды.
## P0-4. Ограничение частоты и поведение при отказе
- Анонимный GitHub API ограничен по частоте. Упёрлись в предел — сообщить об этом прямо, а не выдавать за «обновлений нет».
- Проверка идёт в фоне и **не блокирует интерфейс**. Урок оплачен: `agy models` отвечал десятки секунд и подвешивал окно.
- Отказ проверки не должен мешать работе хаба.
## P0-5. Аудит вторым проходом
1. **Отсутствие данных не выдавать за результат.** Главный риск задания: «сеть недоступна» показать как «у вас последняя версия». Ровно эту ошибку делал прежний код при `404`.
2. **Проверить на настоящем релизе.** Не на заглушке: реальный запрос к API, реальное сравнение коммитов, оба исхода — «есть обновление» и «уже последняя».
3. **Проверить сохранность данных** после установки: аккаунты, настройки, цепочки ролей.
4. **Проверить откат**: подсунуть заведомо битый пакет и убедиться, что установка откатилась и хаб работает.
5. **Побочные изменения** объяснить.
6. **Пропущенный пункт назвать пропущенным.**
---
## Ограничения
- Десктоп (`router/ui/**`) не трогать.
- `apply_update_sync` и список разрешённых хостов не выбрасывать.
- Второй канал доставки не заводить: источник — релизы основного репозитория.
- Версию `0.1.1` не поднимать и тег `v0.1.1` не создавать.
- Правило честности без исключений.
## Критерии приёмки
1. Ветка в `origin`, `git status` чист.
2. `check_for_updates` смотрит на релизы основного репозитория и сравнивает коммиты; проверено настоящим запросом, вывод в отчёте.
3. Оба исхода показаны: «доступно обновление» и «установлена последняя сборка».
4. Недоступная сеть и упёртый предел частоты показываются как **отказ проверки**, а не как отсутствие обновлений; проверено.
5. В вебе видна установленная сборка и время последней проверки; при наличии обновления — отметка и описание.
6. Установка работает на Windows и на Linux **или** честно объявлена неавтоматизированной с показом команды.
7. Контрольная сумма проверяется до запуска установщика.
8. После обновления сервер поднят, аккаунты, настройки и цепочки ролей на месте; проверено.
9. Битый пакет вызывает откат, хаб остаётся работоспособным; проверено.
10. Проверка не блокирует интерфейс.
11. `ruff check .` чисто; релизный гейт не ухудшен.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На `main` сейчас **442 passed, 2 skipped**.
## Главное
Владелец обновляется вручную на трёх машинах по несколько раз в день. Нужно, чтобы программа сама сказала «вышла новая сборка» и поставила её, не потеряв аккаунты и не оставив владельца со старым процессом.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.