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