Поставил 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
16 KiB
Задание 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: сначала
отсоединённый помощник, потом выход текущего процесса. Лаунчер считает хаб
работающим, пока порт отвечает, поэтому поднимать новый, не освободив порт,
бесполезно.
- Хаб порождает помощника (
setsidили отдельная группа процессов на Linux,DETACHED_PROCESSна Windows), передаёт ему путь к уже проверенному пакету и выходит сам, освободив порт. - Помощник: дожидается освобождения порта → запускает установщик → поднимает хаб → завершается.
- Ни одна труба помощника не должна вести в хаб. Это то самое место, где всё
ломается сейчас:
capture_output=Trueделает читателем вывода тот процесс, который установщик собирается снять. Вывод установщика — сразу в файл, а не вPIPE, и не через процесс, которому предстоит умереть. - Помощник не наследует ни stdout хаба, ни его рабочий каталог: хаб исчезнет раньше, чем помощник закончит.
- Помощник пишет свой ход в файл
~/.hermes/updates/apply-<время>.log, чтобы после неудачи было что показать. Пустой отказ без причины — уже было в A59, второй раз не проходит.
P0-2. Владелец видит, что происходит
- Перед выходом статус
restartingс текстом, что хаб сейчас закроется и поднимется сам. Не «установлено» — установка ещё идёт. - Интерфейс переживает разрыв. Опрос
get_update_progressполучит отказ соединения: это ожидаемое состояние, а не ошибка. Показывать «Hermes Hub перезапускается», продолжать пробовать, при возврате — перечитать страницу. - Не молчать бесконечно. Не поднялся за отведённое время — сказать это прямо и назвать путь к журналу помощника.
P0-3. Откат на путях установщика
- Помощник снимает копию установленного до запуска установщика.
- Установщик вернул не ноль или хаб не поднялся за отведённое время — вернуть прежнее и поднять его.
- Причина отказа — с кодом возврата и хвостом вывода — в журнал и на экран при следующем старте.
- Программа обязана остаться работоспособной. Это главное требование пункта: неудачное обновление не имеет права оставить владельца без хаба.
P0-4. Проверка исполнением
Тестами это не ловится: дело в живом переходе между версиями и в том, кто кого снимает.
- Поставить
v0.1.2-b7, обновиться наv0.1.3-b1через интерфейс. Оба релиза опубликованы, установщики иchecksums.txtна месте. Скриншоты: окно, полоса, экран после возврата. - Замерить время от нажатия до готовности.
- Проверить, что работает новый код — по
running_commit, снятому при старте процесса, а не по номеру версии. - Повторить на Windows.
- Сорвать установку намеренно (испорченный установщик) — проверить откат и что хаб жив.
- Проверить, что старый процесс не остался и порт занят новым.
P0-5. Обновление вообще предлагается
Найдено тем же прогоном, до того как дело дошло до установки.
Хаб на v0.1.2-b7 при живом релизе v0.1.3-b1 ответил:
«Установлена сборка новее опубликованного релиза (4fa9939 от 2026-09-02)»,
update_available: false. Обновиться было нельзя вообще.
Причина: deployed_at в deployment_manifest.json пишется установщиком в момент
установки, а не сборки, и сравнивается с published_at релиза. Поставил
старую сборку сегодня — она «новее» любого релиза, и обновление не предложат
больше никогда. Владелец, переставивший сборку руками, выпадает из обновлений
молча.
- Сравнивать сборки, а не дату установки. Дата установки не говорит о том, какой код внутри.
- Если сравнить нечем — предложить обновление, а не промолчать. Молчание здесь дороже лишнего окна: владелец не узнает, что отстал.
- Причину решения показывать. «Установлена сборка новее релиза» — вывод, а не факт; рядом должно стоять, из чего он сделан.
P0-6. Аудит вторым проходом
- Пройти обновление целиком на обеих системах.
- Проверить, что помощник не снимает чужого — только процессы хаба своего пользователя.
- Проверить откат при сорвавшейся установке и что после него хаб отвечает.
- Проверить, что интерфейс не объявляет успех раньше времени — ни на выходе хаба, ни на разрыве связи.
- Побочные изменения объяснить.
- Пропущенный пункт назвать пропущенным. В A59 живая проверка была пропущена молча, при том что релиз для неё был опубликован за девять часов до сдачи.
Ограничения
- Показ и загрузку из A59 не переделывать.
- Проверку SHA-256 и список разрешённых адресов не ослаблять.
- Показ работающего коммита и времени запуска не ломать.
- Фронтенд без npm, без сборки, без фреймворков — по
docs/web-api/CONTRACT.md§1. - Правки ревьюера из
mainне откатывать — включая комментарии. В A59 сняли шесть блоков с объяснением прошлых регрессий, ревьюер возвращал их руками. - Версию
0.1.3не понижать. - Правило честности без исключений: неизвестное —
Н/Дс причиной, а не правдоподобное число и не полоса во всю ширину.
Критерии приёмки
- Ветка в
origin,git statusчист. - Обновление, запущенное из интерфейса, доходит до работающего нового хаба без участия владельца — на Linux и на Windows, подтверждено скриншотами.
- После перезапуска
running_commitсоответствует новой сборке. - Сорвавшаяся установка откатывается, хаб остаётся работоспособным.
- Старый процесс не остался, порт занят новым.
- Журнал помощника пишется, и при отказе на него указывают.
- Обновление предлагается по сравнению сборок, а не по дате установки; переустановка старой сборки не выключает обновления навсегда.
ruff check .чисто; релизный гейт пройден; тестов не меньше 740.- Отчёт:
START_HEAD,FINAL_HEAD,origin/main,git status,X passed / Y skipped / Z failed, тайминги, скриншоты.
Главное
A59 сделал обновление видимым: владелец видит окно и видит загрузку. Дальше механизм обрывается на самом простом — установщик снимает того, кто его запустил и ждёт результата.
Задание про один шаг: чтобы после нажатия «Обновить сейчас» владелец больше ничего не делал.
Порядок сдачи
Передать точный FINAL_COMMIT_SHA.