Обновления и версионирование — план
Закрывает OQ-032 (у приложений нет механизма обновления) в рамках, заданных D-082 (обновления будут; последовательность: закрыть вопросы → поднять Яндекс.Облако → строить обновления против него).
Смежные документы, которые здесь не пересказываются: OVERVIEW.md §1 (почему данные класса не уезжают в облако), protocols/03-teacher-app--portal.md (провод учитель↔портал), protocols/04-feature-flags.md (педагогические флаги — это другой слой, см. §6), portal/PLAN.md (портал как контент-хаб), .memory-bank/tech-details/stack.md (выбор electron-updater + Yandex Object Storage).
1. Решения продукта от 27.08
| Вопрос | Ответ |
|---|---|
| Канал до ноутбуков учеников | Только LAN от учителя + флешка. Интернета у ученических машин нет (ADR-003) |
| Подпись | Apple Developer аккаунт есть. Для Microsoft ничего нет — Windows идёт ZIP-сборками без code signing |
| Что значит «откатить» | «Выкатили обновление с критическим дефектом — должны иметь возможность это починить» |
| Postgres в облаке | Сначала спайк на день (см. portal/PLAN.md) |
Первые три зафиксированы как D-106, D-107, D-108 в .assistant/decisions.md.
2. Чего это стоит: Windows без сертификата
Ответ «зэшки можно так отправлять» верен по факту, но у него два последствия, которые лучше знать заранее, чем обнаружить на первой школе.
Первое. SmartScreen покажет «Неизвестный издатель» при каждом запуске нового билда. Это не блокировка, это лишний экран, который учитель должен уверенно проходить. Значит, в инструкцию для школ это попадает явным пунктом со скриншотом, а не «ну там нажмите всё равно запустить».
Второе, важнее. Штатная проверка подписи в electron-updater на Windows опирается на code signing. Без сертификата она недоступна — то есть из коробки апдейтер принял бы любой архив, который ему подсунули. В школьной сети, где обновление раздаёт учительский ноутбук по LAN, это ровно тот канал раздачи для постороннего, о котором предупреждал D-082.
Решение: подписываем не бинарь, а полезную нагрузку. Ed25519-подпись над архивом обновления, публичный ключ вшит в приложение при сборке, приватный лежит только в CI. Апдейтер проверяет подпись сам, до распаковки. Это:
- закрывает главную угрозу — подмену обновления в школьной сети;
- ничего не стоит и не требует юрлица;
- не убирает предупреждение SmartScreen — это разные механизмы, и путать их не надо.
macOS подписываем и нотаризуем нормально, аккаунт есть.
3. Топология
Портал (Object Storage + манифест)
│ интернет, опционально
▼
Учительское приложение ──── LAN ────► ноутбуки учеников
▲ (интернета нет никогда)
│ флешка
файл-бандл с портала
Три пути, один формат. Учительское приложение — единственная машина, которая может иметь интернет, и оно уже LAN-сервер, к которому ученические ноутбуки подключены (protocols/02). Новых транспортов не требуется: раздача едет по тому же каналу.
Флешка — не запасной костыль, а полноценный первый класс: учитель скачивает на портале один файл бандла, приносит в класс, учительское приложение его импортирует и дальше раздаёт по LAN так же, как если бы скачало из интернета. Один код импорта на оба пути.
APK моста (android-bridge) едет тем же каналом и лежит в том же манифесте; телефон робота обновляется с портала или из файла.
4. Манифест
Один JSON на канал, подписанный тем же ключом:
version семвер сборки
channel beta | stable
platform win-x64 | mac-arm64 | mac-x64 | android
url / size / sha256
signature ed25519 над архивом
min_data_version минимальная версия данных, которую эта сборка понимает (см. §5)
notes что изменилось, по-русски, для учителя
Плюс на уровне канала: current (что раздавать) и blocked (версии, которые раздавать нельзя никогда — см. §7).
5. Версионирование: три независимые оси
Их легко смешать, и тогда «поддержим предыдущие версии» превращается в лозунг. Оси разные:
- Версия приложения — семвер сборки. Меняется каждым релизом беты.
- Версия данных — схема проектов, уроков и настроек на диске. Меняется только когда меняется форма данных. Это и есть та ось, которая отвечает за «уроки и код, написанные на предыдущей версии, продолжают работать».
- Версия провода —
X-Rubrick-Protoвprotocols/02/03, уже существует и живёт своей жизнью.
Правило для оси 2, которое надо принять до первого релиза беты, потому что задним числом его не ввести:
- каждый файл проекта и каждая запись урока несут
dataVersion; - при открытии приложение прогоняет вперёд миграции от своей версии до текущей, по одной, необратимо;
- если
dataVersionновее, чем приложение понимает, оно отказывается открывать файл с внятным сообщением, а не пытается угадать. Молча открыть данные из будущего — это как раз способ их испортить; - перед миграцией — копия файла рядом. Дёшево, и это единственное, что спасёт, если миграция окажется битой.
6. Флаги: два разных слоя, не смешивать
protocols/04 уже описывает флаги — но это педагогические флаги, которыми владеет учитель: blockly_mode_enabled, python_mode_enabled, цель запуска. Scope global/class/student, канал учитель→ученик по LAN.
То, что нужно в админке портала, — другой слой: аварийные флаги релиза, которыми владеет команда РУБРИК. «Выключить у всех блок, который роняет приложение», «спрятать фичу, пока чиним».
Разный владелец, разный жизненный цикл, разный канал. Если положить их в один набор, учитель сможет включить то, что мы выключили аварийно, и наоборот. Поэтому:
- отдельное пространство имён (
release.*), отдельная таблица, отдельная доставка в манифесте; - при конфликте аварийный флаг всегда сильнее педагогического;
- ученическое приложение получает их от учительского вместе с обновлением, а не отдельным каналом.
7. Что значит «починить выкаченный дефект»
Разложим на три разные ситуации, потому что механизмы у них разные.
Дефект замечен до того, как большинство обновилось. Работает kill-switch: помечаем версию как blocked, current откатываем на предыдущую хорошую. Кто не успел — не получит. Кто успел — остаётся на плохой версии до следующего пункта.
Реализовано (D-139, 01.10.2026). Раньше «успевший» не узнавал, что его версия снята. Теперь портал отдаёт список снятых версий GET /api/v1/update/withdrawn, реле учителя его зеркалит и перестаёт раздавать снятую сборку, а приложения ученика и учителя показывают плашку «Эта версия отозвана — обновитесь» (нет замены — «…новая версия скоро появится»). Плашка не блокирует работу и не закрывается. Снять релиз без замены разрешено (аварийный случай), админка предупреждает. Протокол — protocols/06-update-api.md.
Дефект уже у всех. Единственный надёжный ответ — накатить вперёд: собрать исправленную версию с бо́льшим номером и раздать её. Это должно занимать часы, а не дни, поэтому основной инженерный результат этого трека — не механизм отката, а короткий конвейер: коммит → сборка → подпись → манифест → у учителя.
Дефект вскрылся посреди урока. Аварийный флаг из §6 — выключить конкретную фичу, не трогая версию вообще. Быстрее любой пересборки.
Настоящего даунгрейда не делаем. Честная причина: миграции данных из §5 односторонние. Откат приложения назад означал бы либо писать обратную миграцию для каждой версии, либо оставить пользователя с данными, которые старая сборка не понимает — и по правилу §5 откажется открыть. Цена такой возможности несопоставима с «собрать фикс за два часа». Если когда-нибудь понадобится — это отдельное решение с отдельной ценой, а не то, что делается заодно.
8. Фазы
Порядок выбран так, чтобы каждая фаза давала пользу сама по себе, даже если следующая задержится.
Ф0. Видимость версий (~1 день). Версия в обоих приложениях на виду; учительское показывает версии подключённых ученических машин; на портале билды лежат с явным номером. Это D-082 называет «дешёвым шагом»: без него любой звонок из школы начинается с «а какая у вас версия?». Полезно при любом дизайне апдейтера и не зависит от облака.
Ф1. Версия данных (~1–2 дня). Ось 2 из §5: dataVersion, реестр миграций, отказ открывать данные из будущего, копия перед миграцией. Должно уехать в первую же бета-сборку — ввести это задним числом, когда в поле уже лежат файлы без версии, заметно дороже.
Ф2. Конвейер сборки и подписи. CI собирает три платформы, генерирует Ed25519-подпись, кладёт в Object Storage, обновляет манифест. macOS подписываем и нотаризуем. Здесь же — публичный ключ в сборку.
Ф3. Ручной путь. Портал отдаёт файл бандла; учительское приложение умеет его импортировать и проверить подпись. Это уже рабочее обновление школ через флешку — до того, как появится хоть какая автоматика.
Ф4. Раздача по LAN. Учительское приложение отдаёт бандл ученическим по существующему каналу; ученическое ставит и перезапускается. Проверка подписи на стороне ученика обязательна.
Ф5. Автоматическая проверка на стороне учителя. electron-updater против манифеста, когда интернет есть. Никогда не блокирует запуск (ADR-003).
Ф6. Админка: аварийные флаги, current/blocked, статистика версий по устройствам.
9. Открытые вопросы
- Кто нажимает «обновить»? (решено 01.10.2026, D-139, реализовано.) В классе решает учитель: до нажатия «Раздать классу» на вкладке «Обновления» реле не предлагает ученикам новую ученическую сборку (решение хранится по версии: вышла следующая — снова ждёт нажатия; после обновления приложения учителя на этом шаге раздано ничего — нажать надо один раз). Ученик ничего не нажимает: машина в режиме «В классе» берёт сборку только у учителя (портал за его спиной не спрашивается), скачивает в фоне и ставит при ближайшем закрытии или запуске приложения — не посреди урока; в настройках ученика только строка «Обновления в классе раздаёт учитель». Windows: тихий NSIS (
/S) отдельным процессом наwill-quit, а если закрытие не отработало — при следующем запуске, до начала работы. macOS: образ диска открывается при закрытии, перенос в «Программы» — рукой (подпись ad-hoc, D-107); при запуске образ не открывается. Самостоятельно: только портал, установка по кнопке, но скрыта, пока идёт программа («Обновление установится после урока»). Учитель: своё обновление не предлагается, пока подключён хотя бы один ученик (установка перезапускает раздачу и связь со всем классом). Версии приложений учеников (app_verиз presence) видны на вкладке «Обновления». - Частота беты. От неё зависит, нужен ли staged rollout вообще, или на 2000 устройствах и месяце беты хватит «раздали всем».
- Что считать «хорошей» версией для авто-отката
current— только ручное решение человека или есть автоматический признак. - Дельта-обновления.
stack.mdих упоминает. Полный архив Electron-приложения — это сотни мегабайт; по школьному LAN на 25 машин это ощутимо. Но дельты усложняют конвейер, и на бете ими можно не начинать.