hermes-hub/agents/inbox/2026-09-02-A60-update-completes.md
ochenstarik-ui c6981921da docs(agents): A60 — причина разрыва установлена живым прогоном
Поставил v0.1.2-b7 в изолированную песочницу (свои HOME и HERMES_HOME, живая
установка не тронута) и запустил обновление на v0.1.3-b1. Хаб умер через шесть
секунд и за 150 секунд не поднялся. Установка при этом не произошла вовсе:
манифест и код остались на 0.1.2.

В задании было написано, что установщик доживает сиротой и файлы обновляет.
Прогон это опроверг. Настоящая цепочка: хаб зовёт установщик через
capture_output=True, то есть читателем его вывода становится сам хаб; установщик
на шаге [0/6] снимает хаб; у трубы не остаётся читателя; следующий echo даёт
SIGPIPE, и установщик умирает на шаге [1/6], не поставив ничего. Проверено
контрольным опытом: тот же скрипт с выводом в файл доходит до конца, с трубой
умирает. Отсюда следует, что перестановкой schedule_restart делу не помочь.

Тем же прогоном найдено второе: deployed_at пишется в момент установки, а не
сборки, и сравнивается с датой публикации релиза. Хаб на b7 при живом b1 ответил
«установлена сборка новее опубликованного релиза» и обновление не предложил.
Переустановил старую сборку — выпал из обновлений молча. Вынесено в P0-5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EDLuenXmGjWaS2rs8E72En
2026-09-02 19:41:20 +07:00

16 KiB
Raw Permalink Blame History

Задание A60: обновление доводит себя до конца

Дата поступления

2026-09-02

База

origin/main (2f35377). A59 влит: ветка antigravity/a59-visible-update принята с исправлениями ревьюера (285ae7c). Показ и загрузку переписывать второй раз не надо.

git fetch origin --prune
git checkout -b antigravity/a60-update-completes origin/main

В main напрямую не пушить.

Порядок исполнения

Два прохода: Flash реализует, Pro проводит аудит. Пункт P0-6 написан для аудитора.

Зона: применение обновления и перезапуск. Показа и загрузки не касается — там всё принято и проверено.


Что уже сделано — переделывать не надо

A59 довёл обновление до экрана. Ревьюер проверил и принял:

окно при запуске, отказ по версии, молчание без обновлений   работает
полоса хода, честное Н/Д без размера                          работает
отмена с удалением недокачанного файла                        работает
отмена принимается только на проверке и загрузке              работает
SHA-256 обязательна, непроверенный файл не запускается        работает
запись о применённом обновлении только после успеха           работает

Ничего из этого не трогать.


Задача

Владелец нажимает «Обновить сейчас». Пакет скачивается на глазах, сумма сходится, начинается установка — и на этом всё кончается. Хаб не поднимается, окно гаснет, владелец идёт ставить сборку руками. Ровно то, из-за чего писалось A59.


Разрыв, и он один на обеих системах

Установщик снимает тот процесс, который его запустил и ждёт.

install_latest_update делает так:

stop_running_hub()                                  свои процессы, кроме текущего
subprocess.run(["bash", installer], timeout=600)    ЖДЁТ здесь
schedule_restart()                                  сюда управление не доходит

Linux — проверено исполнением, цепочка целиком.

1. хаб: subprocess.run(["bash", installer], capture_output=True)
        stdout установщика — труба, единственный читатель которой сам хаб
2. install-linux.sh, шаг [0/6]: stop_running_hub снимает хаб
        pgrep -u $(id -u) -f "antigravity_provider.router.web|hermes_hub_web_entry"
        никого не исключает, под шаблон попадает тот, кто запустил установщик
3. хаб мёртв  ->  у трубы не осталось читателя
4. следующий echo установщика  ->  SIGPIPE  ->  установщик умирает на шаге [1/6]
5. не установлено ничего; перезапускать нечего

Установщик не доживает до конца — он умирает раньше, чем что-либо поставит. Это не «поставилось, но не запустилось»: манифест и код остаются на прежней сборке. Проверено контрольным опытом — тот же скрипт, та же смерть родителя: с выводом в файл доходит до конца, с capture_output=True умирает.

Отсюда следует, что одной перестановкой schedule_restart делу не помочь: пока установщик пишет в трубу убитого им процесса, он не доживёт до установки при любом порядке вызовов.

Windows — прочитано по коду, живьём не проверялось. installer/HermesHubSetup.cs:280 (StopOwnedRuntime) выглядит аккуратнее: строит $protected — цепочку собственных предков, чтобы не снять того, кто его запустил. Но проверка -notin $protected стоит только на дочерних процессах внутри Stop-HubBranch. Сам процесс-цель снимается безусловно, а хаб под шаблон antigravity_provider\.router\.web подходит. Труба там та же: proc.wait(timeout=600) при Popen без перенаправления вывода. Подтвердить исполнением, а не поверить на слово.

Отката на путях установщика нет вообще: он есть только для .zip (apply_update_sync). Ни .sh, ни .exe при срыве на середине ничего не возвращают.


P0-1. Отсоединённый помощник

Порядок не выдумывать заново — он описан в docstring schedule_restart: сначала отсоединённый помощник, потом выход текущего процесса. Лаунчер считает хаб работающим, пока порт отвечает, поэтому поднимать новый, не освободив порт, бесполезно.

  1. Хаб порождает помощника (setsid или отдельная группа процессов на Linux, DETACHED_PROCESS на Windows), передаёт ему путь к уже проверенному пакету и выходит сам, освободив порт.
  2. Помощник: дожидается освобождения порта → запускает установщик → поднимает хаб → завершается.
  3. Ни одна труба помощника не должна вести в хаб. Это то самое место, где всё ломается сейчас: capture_output=True делает читателем вывода тот процесс, который установщик собирается снять. Вывод установщика — сразу в файл, а не в PIPE, и не через процесс, которому предстоит умереть.
  4. Помощник не наследует ни stdout хаба, ни его рабочий каталог: хаб исчезнет раньше, чем помощник закончит.
  5. Помощник пишет свой ход в файл ~/.hermes/updates/apply-<время>.log, чтобы после неудачи было что показать. Пустой отказ без причины — уже было в A59, второй раз не проходит.

P0-2. Владелец видит, что происходит

  1. Перед выходом статус restarting с текстом, что хаб сейчас закроется и поднимется сам. Не «установлено» — установка ещё идёт.
  2. Интерфейс переживает разрыв. Опрос get_update_progress получит отказ соединения: это ожидаемое состояние, а не ошибка. Показывать «Hermes Hub перезапускается», продолжать пробовать, при возврате — перечитать страницу.
  3. Не молчать бесконечно. Не поднялся за отведённое время — сказать это прямо и назвать путь к журналу помощника.

P0-3. Откат на путях установщика

  1. Помощник снимает копию установленного до запуска установщика.
  2. Установщик вернул не ноль или хаб не поднялся за отведённое время — вернуть прежнее и поднять его.
  3. Причина отказас кодом возврата и хвостом вывода — в журнал и на экран при следующем старте.
  4. Программа обязана остаться работоспособной. Это главное требование пункта: неудачное обновление не имеет права оставить владельца без хаба.

P0-4. Проверка исполнением

Тестами это не ловится: дело в живом переходе между версиями и в том, кто кого снимает.

  1. Поставить v0.1.2-b7, обновиться на v0.1.3-b1 через интерфейс. Оба релиза опубликованы, установщики и checksums.txt на месте. Скриншоты: окно, полоса, экран после возврата.
  2. Замерить время от нажатия до готовности.
  3. Проверить, что работает новый код — по running_commit, снятому при старте процесса, а не по номеру версии.
  4. Повторить на Windows.
  5. Сорвать установку намеренно (испорченный установщик) — проверить откат и что хаб жив.
  6. Проверить, что старый процесс не остался и порт занят новым.

P0-5. Обновление вообще предлагается

Найдено тем же прогоном, до того как дело дошло до установки.

Хаб на v0.1.2-b7 при живом релизе v0.1.3-b1 ответил: «Установлена сборка новее опубликованного релиза (4fa9939 от 2026-09-02)», update_available: false. Обновиться было нельзя вообще.

Причина: deployed_at в deployment_manifest.json пишется установщиком в момент установки, а не сборки, и сравнивается с published_at релиза. Поставил старую сборку сегодня — она «новее» любого релиза, и обновление не предложат больше никогда. Владелец, переставивший сборку руками, выпадает из обновлений молча.

  1. Сравнивать сборки, а не дату установки. Дата установки не говорит о том, какой код внутри.
  2. Если сравнить нечем — предложить обновление, а не промолчать. Молчание здесь дороже лишнего окна: владелец не узнает, что отстал.
  3. Причину решения показывать. «Установлена сборка новее релиза» — вывод, а не факт; рядом должно стоять, из чего он сделан.

P0-6. Аудит вторым проходом

  1. Пройти обновление целиком на обеих системах.
  2. Проверить, что помощник не снимает чужого — только процессы хаба своего пользователя.
  3. Проверить откат при сорвавшейся установке и что после него хаб отвечает.
  4. Проверить, что интерфейс не объявляет успех раньше времени — ни на выходе хаба, ни на разрыве связи.
  5. Побочные изменения объяснить.
  6. Пропущенный пункт назвать пропущенным. В A59 живая проверка была пропущена молча, при том что релиз для неё был опубликован за девять часов до сдачи.

Ограничения

  • Показ и загрузку из A59 не переделывать.
  • Проверку SHA-256 и список разрешённых адресов не ослаблять.
  • Показ работающего коммита и времени запуска не ломать.
  • Фронтенд без npm, без сборки, без фреймворков — по docs/web-api/CONTRACT.md §1.
  • Правки ревьюера из main не откатывать — включая комментарии. В A59 сняли шесть блоков с объяснением прошлых регрессий, ревьюер возвращал их руками.
  • Версию 0.1.3 не понижать.
  • Правило честности без исключений: неизвестное — Н с причиной, а не правдоподобное число и не полоса во всю ширину.

Критерии приёмки

  1. Ветка в origin, git status чист.
  2. Обновление, запущенное из интерфейса, доходит до работающего нового хаба без участия владельца — на Linux и на Windows, подтверждено скриншотами.
  3. После перезапуска running_commit соответствует новой сборке.
  4. Сорвавшаяся установка откатывается, хаб остаётся работоспособным.
  5. Старый процесс не остался, порт занят новым.
  6. Журнал помощника пишется, и при отказе на него указывают.
  7. Обновление предлагается по сравнению сборок, а не по дате установки; переустановка старой сборки не выключает обновления навсегда.
  8. ruff check . чисто; релизный гейт пройден; тестов не меньше 740.
  9. Отчёт: START_HEAD, FINAL_HEAD, origin/main, git status, X passed / Y skipped / Z failed, тайминги, скриншоты.

Главное

A59 сделал обновление видимым: владелец видит окно и видит загрузку. Дальше механизм обрывается на самом простом — установщик снимает того, кто его запустил и ждёт результата.

Задание про один шаг: чтобы после нажатия «Обновить сейчас» владелец больше ничего не делал.

Порядок сдачи

Передать точный FINAL_COMMIT_SHA.