Мы приостановили новые функции и сначала построили для WEOUR OS производственную страховочную сеть

В этом раунде WEOUR OS не получила новых функций — вместо этого были закрыты четыре вещи: хранение релизов, замкнутый контур резервного копирования, временной бюджет планировщика и единый canonical-конвейер публикации. Эта запись объясняет, что изменилось, какие были доказательства на тот момент и чего это не означает.

Это не выпуск функций. Здесь записано, почему WEOUR OS сначала остановила новые функции, чтобы построить слой производственной страховочной сети: резервное копирование, планирование, публикацию, откат.

Область этого раунда
Производственная инфраструктура (запись о строительстве этого раунда): резервное копирование · планирование · публикация · откат
Ритм резервного копирования
Раз в 6 часов (таймер launchd)
Контракт успешного резервного копирования
Четыре шлюза: stream / decrypt / structure / durable
Градация здоровья резервного копирования
HEALTHY ≤ 7 часов · DEGRADED ≤ 12 часов · STALE > 12 часов (FAILED означает неудачу отдельной попытки, а не уровень)
Бюджет stagger планировщика
По умолчанию 30 секунд (нижняя граница 0 / верхняя граница 60), развязан с интервалом tick
Планировщик tick P50 (4 магазина)
119.5 s → 31.9 s
Планировщик tick P95 (4 магазина)
369.9 s → 55.3 s
Загрузка планировщика (duty cycle)
83.7% → 17.1%
Выборка приёмки планировщика
11 tick (4 магазина)
Первый Trial canonical-развёртывания
2026-09-23 неудача, конвейер выполнил откат
Производственная проверка этого раунда
release 20260923-rpg13-7f685ca (CANONICAL, validation / smoke PASS)
Тесты управления публикацией
123 → 157
Состояние хранения на момент наблюдения этого раунда
release 19 · кандидатов на удаление 1 · источник неизвестен 6 · защищено 5 (retention dry-run)
Коммит исправления планировщика
Anxin360/weour-os@e92b059
Коммит исправления readiness / iowait
Anxin360/weour-os@d4cbe01
Верхушка ветки trial Foundation V1.1
Anxin360/weour-os@b8cc817
Коммит управления V1.3
Anxin360/weour-os@7f685ca

Почему сначала остановили новые функции

WEOUR OS уже работает в production с реальными данными: реальные магазины, реальные циклы планировщика, реальные записи о публикациях. Чем больше функций, тем выше цена ошибки; но до этого несколько вещей не были по-настоящему замкнуты:

  • неизменяемые release только накапливались, диск сам не становился чище;
  • резервные копии были, но «действительно ли эта копия успешна» не проверялось автоматически, и не было сигнала здоровья, который можно было бы автоматически потреблять;
  • планировщик выглядел занятым, но занят он был в основном не бизнес-работой.

Это не проблемы функций, а проблемы способа работы. Сначала их исправить — и каждая следующая функция обойдётся дешевле.

Что изменилось

1. Хранение release: неизменяемым release нужна управляемая очистка

Публикация — это неизменяемая цельная сборка: переключение делается символической ссылкой на новый каталог release, откат — переключением обратно на старый каталог. Такая схема делает откат дешёвым, но ценой постоянного роста диска — при аудите каталоги release занимали около 74% использованного диска этой машины (44.12 GB / 59.44 GB использовано), а на том же диске ещё нужно оставлять запас для сборки.

Поэтому очистка была написана как приоритет категорий, а не «удалить самое старое»:

CURRENT > IMMEDIATE_ROLLBACK > DIAGNOSTIC > FAILED_TRIAL > VALIDATED_ANCHOR
      > MIGRATION_BOUNDARY > MANUAL_PROTECT > UNKNOWN_PROVENANCE > ROUTINE

Только ROUTINE попадает в повседневную квотную очистку, остальные категории не удаляются попутно. Особенно важна одна из правил: release неудачного Trial не понижается до ROUTINE только потому, что его commit позже попал в main. Решение принимается только по доказательствам жизненного цикла, а не по имени каталога, временной метке или переписке.

2. Резервное копирование: от «есть копия» к «проверяемой копии»

Форма резервного копирования не переделывалась: хост резервного копирования сам забирает данные, production предоставляет только ограниченный поток принудительных команд, а закрытый ключ для расшифровки никогда не попадает на production-хост. Изменения произошли на стороне забора:

  • Ритм и RPO: раз в 6 часов (таймер launchd), целевой RPO — 6 часов. Ритм объясним и не зависит от дрейфующего интервального таймера.
  • Четыре шлюза: запуск считается успешным только после прохождения всех четырёх шлюзов — stream, decrypt, structure, durable. Код выхода pg_dump, равный 0, сам по себе никогда не является успехом.
  • Неудача есть неудача: при провале любого шлюза запуск получает FAILED, last-success не обновляется, очистка не выполняется.
  • Градация здоровья: HEALTHY (≤ 7 часов) / DEGRADED (≤ 12 часов) / STALE (> 12 часов) выводится из «часов с момента последнего успеха» и может потребляться мониторингом напрямую как код выхода; FAILED описывает отдельную попытку, а не уровень.
  • Тесты с внедрением отказов: покрывают удалённый отказ, слишком маленький артефакт, ошибку расшифровки, недопустимую структуру, наложенный запуск, устаревшую блокировку, политику хранения и повреждённое состояние здоровья.

Последний шаг — вернуть сигнал здоровья в production: после прохождения четырёх шлюзов хост резервного копирования через ограниченную учётную запись отправляет снимок в production; эта учётная запись может писать только один фиксированный файл, не может переключать release, не может перезапускать сервисы и тем более не может читать какие-либо ключи.

3. Планировщик: разделить семантику tick и stagger

Диагностика сначала дала неудобный вывод: в интервале работы 4 магазинов 96.8% настенного времени tick уходило на детерминированный stagger-сон — из 2610 секунд 84 секунды были реальной работой. В расчёте на каждое action сам сон занимал 4150 секунд/час, почти 115% настенного времени.

Корневая причина — смешение семантики: окно stagger было привязано к интервалу tick (180 секунд), а фиксированные задержки нескольких action на одном последовательном runner складывались последовательно.

Исправление разделило две вещи: интервал tick используется только для отчётного cadence и больше не участвует в расчёте stagger; у stagger есть собственный бюджет, по умолчанию 30 секунд, допустимые значения 0 / 30 / 60, недопустимые значения откатываются к значению по умолчанию. Измерения после развёртывания:

  • tick P50 119.5 секунды → 31.9 секунды, P95 369.9 секунды → 55.3 секунды;
  • загрузка (duty cycle) 83.7% → 17.1%;
  • запланированный сон совпадает с фактическим, остальное время — объяснимая работа с API и DB;
  • timer, cadence, переключатели магазинов и бюджет параллелизма не менялись.

4. Единый canonical-конвейер публикации

Публикация — больше не цепочка ручных команд, а упорядоченный воспроизводимый конвейер:

provenance → preflight → release lock → scheduler quiet window → source
  → build backend → monitored build → immutable release + manifest
  → candidate validation → rollback readiness → atomic switch → restart
  → readiness → functional smoke → state write → history → retention dry-run

Несколько мест сделаны намеренно: сборка выполняется только в тихом окне планировщика; переключение атомарно; цель отката проверяется до и после переключения; код выхода systemctl restart явно записывается как «юнит запущен», а не «приложение готово».

5. Первый Trial провалился — и был полностью сохранён

Первый Trial canonical-развёртывания прошёл свой путь успеха, затем провалился на этапе smoke, и конвейер выполнил свой canonical-откат.

Причина не в приложении: systemctl restart при возврате означает лишь, что юнит запущен, а не что порт уже привязан. Тогдашний smoke был немедленной HTTP-проверкой без какого-либо ожидания readiness, поэтому он увидел отказ соединения и ошибку шлюза.

Важен именно способ обработки: никто не обошёл конвейер, чтобы вручную исправить этот шаг. Конвейер остановился по своему контракту отказа и выполнил откат, этот release был сохранён для диагностики, а на этапе очистки не было выполнено ни одного удаления. Production продолжал нормально обслуживаться на release до переключения.

Последовавшее исправление сделало readiness ограниченным опросом, а мониторинг сборки — реально измеряющим iowait. При повторном прогоне прежний режим отказа был корректно поглощён новой логикой как временная неготовность, а не как неудача развёртывания.

6. Управление: оставить доказательства и «кто может публиковать», и «что было опубликовано»

Правила этого раунда добавлены для будущих себя:

  • canonical deploy only: повседневная публикация идёт только через конвейер; исключения должны быть явно помечены.
  • deploymentMethod: каждая новая запись жизненного цикла несёт перечисление метода, исторические записи дописываются, а не переписываются; неопределимые исторические записи помечаются UNKNOWN_LEGACY и никогда не угадываются как CANONICAL.
  • Классификация неудачного Trial: FAILED_TRIAL определяется по доказательствам жизненного цикла и не занимает повседневную квоту очистки.
  • Приоритет диагностических доказательств: перед удалением нужно записать обратно диагностическую запись уровня KB без ключей и проверить её; если записать не удаётся — удаление отклоняется.

На точке наблюдения этого раунда (production release 20260923-rpg13-7f685ca) политика хранения представлена в dry-run: запись показывает 19 release, из них 1 кандидат на удаление, 6 с неизвестным источником, 5 защищённых. Очистка происходит только при полных доказательствах (цель подтверждена как неиспользуемая, диагностическая запись записана обратно и проверена).

Как мы это проверили

Способ проверки каждого звена по возможности опирается на перепроверяемые доказательства, а не на «выглядит нормально»:

  • резервное копирование проверено тестами с внедрением отказов и реальными прогонами четырёх шлюзов, сигнал здоровья потребляется мониторингом;
  • исправление планировщика сначала смоделировано в изолированной среде с измеренными offset, затем после развёртывания сравнено до и после на 11 реальных tick;
  • конвейер публикации проверен тем, что ему дали самостоятельно выполнить реальную публикацию: readiness пройден, functional smoke прошёл по шести маршрутам, состояние записано конвейером, и только затем выполнен dry-run retention;
  • правила управления покрыты тестами — от «неудачный Trial не понижается» до «публикатор отклоняет аномальную нагрузку».

Что это не значит

Это не выпуск функций и не означает, что инфраструктура завершена.

В записи этого раунда ограничения резервного копирования прописаны явно: ключ расшифровки существует в единственном экземпляре и находится на той же машине, что и шифротекст; нет второй копии в другом месте; нет PITR, RPO — это и есть ритм в 6 часов; у резервного копирования медиа нет политики хранения; роли и глобальные объекты не входят в dump. Это известные компромиссы, а не проигнорированные детали.

Исправление планировщика решает только вопрос «куда уходит время», это не оптимизация параллелизма или производительности.

Управление также не покрывает всю историю: на точке наблюдения этого раунда всё ещё существуют старые release с неопределимым источником, они консервативно сохранены.

Эта запись относится к раунду строительства с 2026-09-22 по 2026-09-23. Все производственные состояния в тексте — факты на точке наблюдения этого раунда, а не текущее состояние в любой последующий момент; последующие производственные изменения вне рамок этой статьи.

И наконец, это не обещание будущих версий, сроков или возможностей. Здесь записано уже произошедшее обновление инфраструктуры и доказательства, которые его подкрепляют.

Где доказательства

Каждое фактическое утверждение в этой записи указывает на объект, который можно перепроверить: соответствие коммитов, идентификаторов развёрнутых release и количества тестов зарегистрировано в таблице фактов выше, а также в evidence-записи этой статьи. Основной текст лишь объясняет смысл и не повторяет перечень идентификаторов.

WEOUR / BUILD

Примите участие в сборке

Если вы решаете реальные задачи в трансграничной торговле, запасах, складе, фулфилменте или автоматизации — расскажите нам о своей задаче.

Оставить запрос Следите за WEOUR / сообщество — позже