VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
Reading time
7 min
Rendered
Read aloud
never

Находки на 2026-08-09 — материал для размышления

01Это опциональный вход. Ты не обязан соглашаться. Найденное несогласие с любым пунктом ниже — самый ценный результат твоей работы, если оно обосновано.

02Всё, что помечено «ИЗМЕРЕНО», получено чтением нашего дерева. Всё остальное — из веб-исследования с дословными цитатами; ты веб не видишь, поэтому проверить эти пункты не можешь и не должен делать вид, что можешь.

Часть 0. Задача

03vibevm публикует каталог пакетов: набор JSON-файлов в git-репозитории, который читают ЧУЖИЕ инструменты. Данные В ПОКОЕ, не сетевой протокол. Внешних потребителей пока ноль — значит ломающее изменение сейчас бесплатно и потом никогда не будет.

04Владелец решил: описать формат схемами и генерировать код из них. Замер перед постройкой нашёл, что цена другая, чем в таблице, по которой решали.

05Новое (важно): владелец хочет применить те же выводы к парсеру vibe.toml — манифеста, который пишут РУКАМИ и который едет внутри каждого опубликованного пакета.

Часть 1. ИЗМЕРЕНО — наше дерево

06Три долговечных формата, и ни один не решён одинаково:

07
версия в данных кто-то ветвится по ней незнакомый ключ заповедник для чужих ключей
vibe.lock есть (5) да, отвергает отвергается нет
каталог индекса есть (1) нет, никто отвергается нет
vibe.toml НЕТ ВОВСЕ — отвергается нет

08Каталог, детально (crates/vibe-index/):

  • 0923 типа на проводе; из них 18 — файлы каталога (17 структур + 1 объединение).
  • 86 полей.
  • 14 полей-коллекций, где пустое неотличимо от отсутствующего — skip_serializing_if = "…is_empty".
  • 1 объединение без тега, и оно единственное: RepomdFileEntry в types/repomd.rs — либо {"kind":"directory","entries":N}, либо {"size":N,"sha256":"…"}. Общего поля-признака нет; читатель угадывает по набору ключей. Это в манифесте каталога — файле, который читатель открывает ПЕРВЫМ.
  • deny_unknown_fields в 15 местах. Каталог перечитывается ради перезаписи на 6 путях. Следствие: незнакомое поле не теряет данные — оно не даёт прочитать каталог вообще; сервер грузит каталог на старте, значит старый сервер не запустится на каталоге, записанном новым.
  • 5 закрытых словарей, у всех неизвестное значение = ошибка разбора. #[serde(other)] нет нигде в дереве.
  • Версия каталога — ярлык, а не переключатель: ни одного сравнения.
  • Спека ПРЯМО ОБЕЩАЕТ «читатели старой версии спокойно игнорируют незнакомые поля». Код делает противоположное. Обещание помечено «реализовано».
  • Клиент (vibe-registry/src/index_client/wire.rs) уже терпим — читает своими view-структурами. То есть наружу мы правильны, а сами себе строги.

Часть 2. Из исследования — наши соседи (данные в покое)

10PyPI, авария PEP 714 — прямое попадание в нашу форму. Поле, которое бывает «либо булево, либо словарь» — то есть объединение без тега. pip падал с AttributeError: 'dict' object has no attribute 'partition'. Баг прожил 8 месяцев, попал в дистрибутивы и образы. Ключевое:

11

«сломанная таким образом версия pip не может установить с PyPI вообще ничего — включая новую, починенную версию pip»

12Починка уничтожила бы путь к починке. Пришлось менять спецификацию и переименовывать поле. Оценка альтернативы «подождать, пока вымоется» — 5+ лет.

13«Поле, которое ты отдаёшь восемь месяцев, ты отдаёшь навсегда.» PyPI до сих пор, три года спустя, отдаёт опечатанное имя ключа. Переименование обошлось дёшево ровно потому, что читателей было трое и их можно было обзвонить.

14Контракт версии (PEP 629), нормативный:

  • 15старшая версия выше известной → ОБЯЗАН отказаться с внятной ошибкой;
  • младшая выше известной → СЛЕДУЕТ предупредить и продолжить;
  • версии нет → ОБЯЗАН считать 1.0.

16Решение из их спора: поднимать младшую версию имеет смысл, только если поля, которые она вводит, обязательны на этой версии. Иначе клиенту незачем её проверять.

17Терпимость нормативна и асимметрична: писателю «можно добавлять», читателю «ОБЯЗАН игнорировать незнакомые ключи». Режима отказа нет вообще.

18Три состояния различены намеренно: «если ключа нет — файл метаданных может существовать, а может и нет; если значение истинно — существует; если ложно — нет». Отсутствие = НЕИЗВЕСТНО, присутствующее ложное = известно-что-нет.

19Пустая коллекция пишется, а не опускается: словарь хэшей «ОБЯЗАН присутствовать, даже если хэшей нет».

20Ломающее изменение — новый ПУТЬ, а не новая старшая версия в том же документе. К этому независимо пришли PyPI и NuGet.

21Заповедник для чужих ключей: ключи с подчёркиванием зарезервированы, «ни один будущий стандарт не назначит им смысла».

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

23NuGet — тихий отказ на шесть лет: фильтр по типу пакета «молча игнорировался всеми совместимыми источниками столько, сколько это свойство существует публично», и починка 2026 года сама стала ломающей.

24Homebrew — как не надо: схемы нет, версии в данных нет, обещание стабильности — одна фраза в чужом документе, две попытки завести проверку схемы в сборке закрыты нереализованными.

Часть 3. Из исследования — механика эволюции

25У протокол-буферов две кодировки, и политика противоположна. Двоичная терпима и сохраняет незнакомое. JSON-овая: «разборщик должен по умолчанию отвергать незнакомые поля». На жалобы ответ: «используйте двоичную». Мы наследуем весь набор проблем их JSON-кодировки и не имеем их выхода.

26Развороты, оплаченные чужой болью:

  • 27required убрали совсем: «никогда не знаешь, сколько проживёт тип и не придётся ли кому-то через четыре года заполнять твоё обязательное поле пустой строкой».
  • Возможность отличить «поля нет» от «поле по умолчанию» убрали и вернули через ~5 лет «в ответ на отзывы пользователей». Для списков и словарей так и не вернули — там «пусто» и «нет» слиты навсегда.
  • Сохранение незнакомых полей убрали и вернули; в обсуждении 19 месяцев спустя выяснилось, что исходное обоснование было слабее, чем писала документация.
  • Словари переключили с закрытых на открытые «именно из-за неожиданного поведения, которое вызывают закрытые». Этот разворот прижился, потому что был оправдан демонстрируемой порчей данных, а не удобством.

28Avro — другая модель: кладёт схему писателя вместе с данными, читатель согласует свою схему с писательской. В сети это дорого; в git-репозитории это стоит указателя, и история схемы лежит в той же истории, что и данные.

29Словарь совместимости (его половина индустрии путает):

  • 30BACKWARD — новый читатель читает СТАРЫЕ данные;
  • FORWARD — старый читатель читает НОВЫЕ данные.

31Наш случай — строго FORWARD. Добавление поля безопасно назад и враждебно вперёд; удаление зеркально. Терпимый читатель — то, что превращает каждое в полную совместимость.

32Принятая по умолчанию проверка совместимости НЕтранзитивна, потому что предполагает, что старые сообщения вымываются. Для репозитория это ложно.

33RFC 9413 (2023) отзывает принцип «будь либерален»: терпимость «входит в патологический цикл обратной связи», «дефект закрепляется как стандарт де-факто». Оговорка, которую теряют почти все цитирующие: он бьёт по терпимости к НЕСООТВЕТСТВУЮЩЕМУ входу, а не к ОБЪЯВЛЕННЫМ точкам расширения.

Часть 4. Из исследования — клиенты и переносимость

34Критерий переносимости: практика переносится тогда и только тогда, когда она — свойство артефакта или твоей собственной дисциплины. Всё, что свойство отношений (переговоры, наблюдение, принуждение, трансляция), умирает при соприкосновении с файлом в git.

35НЕ переносится, и это самая часто ошибочно переносимая практика: «бесверсионная аддитивная эволюция». Ей нужен запрос. Файл — это ответ, у которого не было вопроса.

36Когда эти компании сталкиваются с потребителями, которых не контролируют, они версионируют. Meta держит бесверсионный внутренний интерфейс и явно версионированный внешний с двухлетним сроком. LinkedIn сделал так же и опубликовал, что бесверсионная публичная модель провалилась.

37Про словари консенсуса НЕТ. Google: добавление значения — не ломающее. Kubernetes (вышел из Google): добавление значения — не совместимое. LinkedIn: считайте обратно-несовместимым. Четыре организации независимо изобрели третью категорию — «опасное изменение».

38Механическая причина, по которой чинить надо заранее: незнакомое ПОЛЕ можно пропустить, у него есть адрес. Незнакомое ЗНАЧЕНИЕ словаря обязано быть положено в типизированную ячейку, перечисляющую только известное. Любая починка расширяет ячейку заранее. Ни одну нельзя внедрить в читателей, которые уже разошлись.

39Все распространённые разборщики по умолчанию бросают исключение. Две терпимые по умолчанию библиотеки — оба случая, когда издатель схемы сам писал генератор кода читателя.

40Единственный опубликованный рецепт для НЕКОНТРОЛИРУЕМЫХ потребителей (LinkedIn): не расширять словарь, а добавить новое необязательное поле с новым перечислением и поддерживать старых клиентов со старыми символами бессрочно. И отдельно: этот приём «не работает для данных, сохранённых в Avro» — то есть в покое становится хуже.

41Разбор аварии Cloudflare (ноябрь 2025) — ближайший аналог нашего случая. Безобидное изменение прав добавило строк в сгенерированный файл конфигурации, файл вырос вдвое, у развёрнутых потребителей был зашит предел → глобальная авария. Версионный перекос изменил вид отказа: новые прокси возвращали ошибки, а старые ошибок не видели и считали неверно — «всему трафику выставлялся ботовый балл ноль». Старый читатель работал молча, неправильно и уверенно. Их вывод в починку: ужесточить приём собственных сгенерированных файлов так же, как для пользовательского ввода.

42Переносится (внедрять первым): статическая проверка совместимости в сборке — ей не нужен ни сервер, ни телеметрия.

43Аргумент за ОБЯЗАТЕЛЬНУЮ версию: у Discord бесверсионный маршрут по умолчанию годами заморожен на устаревшей версии — они навсегда пожертвовали возможностью сдвинуть собственное умолчание. Бесверсионный формат не эволюционирует, он только нарастает.

44Защита Google — не правило, а БЮДЖЕТ: «перечисления должны получать новые значения нечасто… не чаще раза в год. Для часто меняющихся использовать строку».

Часть 5. Моя текущая рекомендация — оспорь её

  1. 45Каждое объединение — тегированное.
  2. Отсутствие означает «не записано»; известное пустое пишется явно пустым списком. (null не нужен: в TOML его нет, и правило, выживающее в обоих форматах, скорее верное.)
  3. У каждого закрытого словаря — запасное значение «неизвестное».
  4. Строгость не один рычаг: она зависит от того, кто написал файл. Проверяй строго на входной двери (vibe.toml, руками, — ловим опечатку), неси терпимо дальше (каталог, машиной, — переживаем будущее). Разделять пространством имён, а не уровнем строгости.
  5. Версия формата несёт контракт: старшая — отказ, младшая — предупреждение, отсутствие — считать первой.
  6. Имена полей не переиспользуются; есть список отставленных.
  7. Порядок работ: каталог — полигон (ломать даром, здесь строится машинерия), манифест — боевое применение (дорого, но машинерия уже обкатана).

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/research/schema-evolution-2026-08/05-boss-findings-digest

.md.xmlllms.txt