# МЕГАОТЧЁТ — эволюция форматов vibevm

[p01] _Сведён 2026-08-09. Вход: один замер дерева, четыре веб-исследования, четыре
независимых разбора GLM. Всё сырьё — файлы 01–09 в этом каталоге._

## 0. Как читать этот документ

[p02] Раздел 5 — главный. Там разобраны шесть противоречий, и три из них — ошибки в
моём собственном своде, найденные воркерами. Разделы 1–4 — материал, раздел 6 —
предложение, раздел 7 — то, что решает владелец, раздел 8 — задание ревьюеру.

[p03] Разметка источника обязательна и сохранена: `[ИЗМЕРЕНО]` — чтение нашего дерева
с `файл:строка`; `[ВЕБ]` — веб-исследование с дословной цитатой и датой
обращения; `[GLM]` — суждение воркера без доступа в сеть.

## 1. Вопрос и как он изменился

[p04] **Начали с:** каталог пакетов публикуется как JSON в git-репозитории; решено
описать его схемами и генерировать код; где провести границу, если одна форма в
языке схем невыразима.

[p05] **Пришли к:** граница генерации — наименее интересная часть. Настоящий вопрос —
**какова политика эволюции долговечных форматов vibevm**, потому что форматов
три, решены они по-разному, и никто этого не решал.

[p06] **Владелец добавил** третий поворот: те же выводы применить к парсеру
`vibe.toml`. Это превращает вопрос из «дизайн одного файла» в «один контракт на
три файла».

## 2. Источники

[p07]
| файл | что это | чем получено |
| --- | --- | --- |
| `01-measure-our-wire-format-claudez.xml` | 23 типа, 86 полей, все объединения, все словари, кругооборот | GLM по дереву |
| `02-research-package-indexes-web.xml` | crates.io, OCI, Debian, Go, npm, Maven, RPM, PyPI, NuGet, Homebrew | веб, дословные цитаты |
| `03-research-serialization-mechanics-web.xml` | протобуф, Avro, Thrift, JTD, Cap'n Proto, JSON Schema, Kafka | веб, дословные цитаты |
| `04-research-client-survival-web.xml` | Meta, Twitter, Google, LinkedIn, Badoo, GraphQL, Discord, Cloudflare | веб, дословные цитаты |
| `05-boss-findings-digest.xml` | мой свод — **вход для GLM, содержит три ошибки, см. §5** | я |
| `06-glm-neighbours-and-principles.xml` | принцип за приёмами; отличия нашего случая | GLM |
| `07-glm-format-mechanics.xml` | пять форм тега; скепсис к Avro; граница терпимости | GLM |
| `08-glm-clients-and-vocabularies.xml` | формы запасного значения; **где наш читатель поймёт неверно** | GLM |
| `09-glm-manifest-and-unified-policy.xml` | манифест; довод против единой политики; чекер | GLM |

## 3. Наше состояние — измерено, расхождения разрешены

[p08] Три долговечных формата, три разных ответа, ни один не выбран сознательно:

[p09]
|  | версия в данных | кто-то ветвится | незнакомый ключ |
| --- | --- | --- | --- |
| `vibe.lock` | есть (5) | **да, отвергает** | отвергается |
| каталог индекса | есть (1), на КАЖДОЙ записи | **нет, никто** | отвергается |
| `vibe.toml` | **нет вовсе** | — | отвергается |

[p10] Каталог `[ИЗМЕРЕНО]`:

- [p11] **23 типа** на проводе; 18 — файлы каталога (17 структур + 1 объединение).
- **86 полей**; из них **21** таково, что «пусто» неотличимо от «нет поля»
  (14 коллекций + 7 вложенных структур — см. §5.2).
- **1 объединение**, и оно **полутегированное**, а не нетегированное (§5.1).
- **`deny_unknown_fields` в 15 местах.** Каталог перечитывается ради перезаписи
  на **6 путях**. Незнакомое поле не теряет данные — оно **не даёт прочитать
  каталог**; сервер грузит его на старте, значит старый сервер **не запустится**
  на каталоге, записанном новым.
- **5 закрытых словарей** в крейте (4 в `types/`, см. §5.3), у всех неизвестное
  значение — ошибка разбора. `#[serde(other)]` нет нигде.
- **Версия — ярлык, а не переключатель:** ни одного сравнения.
- Спека обещает терпимость к незнакомым полям, помечено «реализовано». Код
  делает противоположное. `[GLM]` добавляет: **спека противоречит и самой
  себе** — два взаимоисключающих утверждения, оба «реализовано», а код делает
  третье. Я видела одну трещину из трёх.

[p12] **Четыре места, где старый читатель разберёт успешно и поймёт неверно** `[GLM]`
— это отказ вида Cloudflare, найденный у нас: `repomd.rs:42-55`,
`memory.rs:262-280`, `inverted.rs:66,75`, `subskill.rs:144-146`. Подробности —
файл 08. **Это самая ценная находка всей серии.**

## 4. Что сошлось у зрелых систем

[p13] Тринадцать сходимостей в файле 02; здесь несущие.

1. [p14] **«Не ломать, только добавлять» — универсально**, и это дисциплина ПИСАТЕЛЯ.
   Следствие: формат вечно копит необязательные поля, и **сузить тип после
   публикации нельзя никогда**.
2. **Добавление поля НЕ поднимает версию.** Версию поднимает только то, из-за
   чего старый читатель **неверно поймёт уже существующие данные**.
3. **Нетегированные объединения — самый надёжный источник сожалений**, и
   расплата всегда одна: **новое поле**, ни разу — починка на месте.
4. **Незнакомые поля игнорируют на практике, но почти никто не пишет этого
   нормативно.** «Снисходительность, которую вы не записали, — это
   снисходительность, которой у вас нет.»
5. **«Отсутствие» у всех означает значение по умолчанию; «не записано» не
   моделирует почти никто**, и внедрить это задним числом **невозможно, потому
   что отсутствие уже что-то означает**.
6. **Оба края останавливают эволюцию.** Maven замёрз от строгости; RPM — от
   отсутствия версии. Работает **только** терпимый читатель **плюс** сигнал
   версии.
7. **Сигнал версии вешают на наименьшую единицу** — crates.io версионирует
   запись, не файл. У нас так уже сделано.
8. **Ломающее изменение — новый ПУТЬ**, а не новая старшая версия в том же
   документе. Независимо: PyPI, NuGet.
9. **Контракт — строка-идентификатор, а не то, что она обозначает.**
   Единственный смертельный слом RPM за двадцать лет — переопределение смысла
   существующего тега, не добавление новых.
10. **Авторский документ ≠ опубликованный документ.** Независимо: Maven, npm,
    crates.io, Debian. **Прямой ответ на вопрос владельца про манифест.**
11. **Версионный шлагбаум защищает только тех читателей, у кого он уже есть.**
    Cargo отгрузил поле версии недокументированным за годы до нужды. Go
    вывернулся, бэкпортировав проверку, — что мог себе позволить только потому,
    что выпускает один инструмент.
12. **Версия в заголовке протокола отвергнута именно из-за нашего случая** —
    чтобы зеркала могли оставаться тупым файловым сервером.
13. **Запасное значение в словарях нельзя внедрить задним числом** — незнакомое
    значение обязано быть положено в ячейку, перечисляющую только известное.

## 5. Противоречия и их разрешение

### 5.1 Моё описание объединения было неверно — нашли трое независимо

[p15] Я писала: «общего поля-признака нет, читатель угадывает по набору ключей».

[p16] **Проверено мной по дереву** (`types/repomd.rs:41-54`): вариант `Directory`
**несёт** `kind: DirectoryTag`, и доккомментарий прямо говорит, что тег стоит
там **намеренно**, чтобы сопоставитель различал однозначно. Тега нет у варианта
`File`.

[p17] **Разрешение.** Объединение **полутегированное**, а не нетегированное. Это
меняет две вещи:

- [p18] Аналогия с аварией PyPI **структурно натянута** `[GLM ×2]`: там сломался
  разбор значения переменного ТИПА (строка против словаря), у нас оба варианта —
  объекты, и один помечен.
- **Починка дешевле, чем я говорила**: перейти с `untagged` на явный тег — по
  сути один атрибут, и **именно это разблокирует исходный вопрос**, потому что
  тип становится выразимым в языке схем ровно в тот момент, когда мы перестаём
  брать `untagged` `[GLM]`.

[p19] Пять конкретных форм с ценой каждой — файл 07.

### 5.2 «14 полей» против «21»

[p20] Замер: 21 поле с пропуском-когда-пусто, из них 14 коллекций, 7 вложенных
структур. Мой свод взял 14.

[p21] **Разрешение: оба числа верны, вопросы разные.** Коллекций — 14. **Полей, где
«пусто» неотличимо от «нет поля», — 21**, потому что вложенные структуры
схлопываются точно так же `[GLM]`. Решение принимается по **21**, и мой свод
занизил вход.

### 5.3 «5 словарей» против «4» и «3»

[p22] Замер — 5, один воркер — 4, другой — 3.

[p23] **Разрешение: расхождение периметра, не факта.** 4 живут в `types/`, пятый
(`BindingSite`) — в `index/inverted.rs`. Кто считал только `types/`, получил 4;
кто считал только те, что реально едут в файлы каталога, — 3.

[p24] Это наш собственный стоячий закон: **число без названного периметра —
это число, которое следующий читатель выведет неправильно.** Я его нарушила
первой.

### 5.4 Противоречие внутри самого корпуса: когда поднимать версию

[p25] crates.io добавляет поля **без** поднятия. PyPI **требует** поднимать младшую.
Обе политики в исследовании одобрены. Это настоящая нестыковка.

[p26] **Разрешение: они отвечают на разные вопросы, потому что версия у них служит
разному.**

[p27]
```
Версия как ШЛАГБАУМ (crates.io)
  смысл: «сможешь ли ты вообще интерпретировать эту запись»
  правило: старше моей — пропусти ЗАПИСЬ, остальное читай
  добавление поля этого не меняет  →  НЕ поднимаем

Версия как ДЕКЛАРАЦИЯ ВОЗМОЖНОСТЕЙ (PyPI)
  смысл: «какие поля ты вправе здесь ожидать»
  правило: клиент смотрит версию, чтобы знать, что будет
  чтобы это работало, поля версии должны быть ОБЯЗАТЕЛЬНЫ  →  поднимаем
```

[p28] Одним целым числом обе роли не сыграть. **Сначала решаем, ЧЕМ у нас служит
версия, и только потом — когда её двигать.**

[p29] **Для каталога шлагбаум сильно лучше**, и мы к нему уже ближе: версия у нас
стоит на **каждой записи**. Каталог — это множество независимых записей;
пропустить одну непонятную запись — деградация, отвергнуть весь файл — отказ.

### 5.5 Avro-в-git — отзываю

[p30] Я говорила: схема писателя рядом с данными, а у нас схема и данные в одной
истории git, значит почти бесплатно.

[p31] **Два воркера независимо это разобрали, и они правы.** Каталог и схемы — в
**разных** репозиториях и историях; внешний читатель без доступа к нашему репо
модель не выполнит; запись, скопированная наружу, теряет связь со схемой. И
главное — **это противоречит моему же критерию**: «свойство отношений умирает
при соприкосновении с файлом», а «сходи забери мою схему» есть отношение.

[p32] **Отзываю. Терпимый читатель дешевле, и он у нас уже написан.**

### 5.6 Стратегическая поправка: механизмы против обязательств

[p33] Мой свод предлагал принять дисциплину целиком сейчас. Возражение `[GLM]`:
при нуле читателей дешевле **ломать и переименовывать**, а преждевременная
заморозка продаёт наше единственное преимущество перед зрелыми соседями.

[p34] **Разрешение — разделить два разных вида решений:**

[p35]
```
МЕХАНИЗМЫ — обязаны существовать ДО первого читателя,
            потому что задним числом не внедряются:
  · тег на объединении
  · запасное значение в словарях
  · терпимый читатель
  · версия с контрактом
  · решённое значение «отсутствия»

ОБЯЗАТЕЛЬСТВА — начинаются В МОМЕНТ ПУБЛИКАЦИИ, не сейчас:
  · никогда не переименовывать
  · вечные псевдонимы
  · список отставленных имён
  · сроки устаревания
```

[p36] Пока читателей ноль, обязательства стоят свободы и не покупают ничего.
Механизмы — наоборот: **их окно закрывается вместе с публикацией.**

### 5.7 Поправка к «строгость зависит от автора файла»

[p37] Я предлагала: рукописный файл — строго, машинный — терпимо.

[p38] **Возражение `[GLM]`: из файла нельзя узнать, кто его написал.** Я сама задала
этот вопрос воркеру и получила ответ «никак».

[p39] **Разрешение:** критерий не «кто написал», а **какой это файл** (это известно
всегда) и **в каком пространстве имён ключ** (это выразимо). Формулировка
переписывается:

[p40]
```
Файл, который редактирует человек   → незнакомый ключ в НАШЕМ
(vibe.toml)                            пространстве = ошибка (ловим опечатку)
                                     → чужое пространство = не наше дело

Файл, который пишет машина          → незнакомое поле = игнорировать
(каталог)                              (переживаем будущее)
```

## 6. Предложение

[p41] **Механизмы — до первой публикации, потому что окно закроется.**

1. [p42] **Объединение делается симметрично тегированным.** Переход с `untagged` на
   явный тег; вариант `File` получает свой тег. Разблокирует исходный вопрос про
   генерацию: тип становится выразимым в языке схем.
2. **Каждый закрытый словарь получает запасное значение** с сохранением исходной
   строки. Пять словарей. Без этого добавление седьмого вида пакета — ломающее
   изменение для каждого внешнего читателя, навсегда.
3. **Читатель каталога становится терпимым к незнакомым ПОЛЯМ** — снять
   `deny_unknown_fields` с типов каталога. Это один атрибут, и он же делает
   правдой обещание, которое спека уже даёт. Писатель остаётся строгим.
4. **Отсутствие получает решённый смысл, по одному правилу на оба формата:**
   отсутствие = «не записано»; известное пустое пишется явно пустым списком.
   Решение принимается по **21** полю, не по 14. `null` не нужен — его нет в
   TOML, и правило, выживающее в обоих форматах, скорее верное.
5. **Версия каталога становится шлагбаумом на уровне записи:** старше моей —
   запись пропускается с сообщением, остальные читаются. Ветвление появляется
   там, где сегодня только присваивание.
6. **`vibe.toml` получает версию, и её отсутствие — НЕ «считать первой».**
   Для рукописного формата «версии нет» может значить «стёрли по ошибке».
   Эталон стоит в этом же проекте: `vibe.lock` требует версию и отвергает
   отсутствие.
7. **Заповедник для чужих ключей** в манифесте — своя секция, которую vibe не
   трогает и не проверяет.
8. **Один чекер на три формата** — набор машинно-проверяемых правил (форма — в
   файле 09). Без него три набора правил разойдутся так же, как уже разошёлся
   словарь видов пакетов, записанный во многих местах.

[p43] **Обязательства — с момента первой публикации, не раньше.** До неё ломаем и
переименовываем свободно: это единственное преимущество, которого у зрелых
соседей уже нет.

[p44] **Порядок:** каталог — полигон (ломать даром, здесь строится машинерия);
`vibe.toml` — боевое применение (дорого, но машинерия уже обкатана). Один
воркер оспаривает и этот порядок — разбор в файле 09.

## 7. Что остаётся владельцу

1. [p45] **Чем служит версия каталога — шлагбаумом или декларацией возможностей?**
   §5.4. Рекомендация: шлагбаум на уровне записи.
2. **Манифест в опубликованном пакете и манифест проекта — один формат или
   два?** Индустрия независимо пришла к двум (авторский документ ≠
   опубликованный), четыре раза. Один воркер возражает, что `vibe.toml` у нас
   вообще не внешняя поверхность — файл 09.
3. **Принимаем ли мы, что отсутствие означает «не записано»**, зная, что у всех
   соседей оно означает «значение по умолчанию» и что переиграть нельзя?
4. **Сроки обязательств** — с какого момента формат считается опубликованным и
   ломать больше нельзя.

## 8. Задание ревьюеру

[p46] Разобрать этот отчёт и девять исходных файлов; проверить разрешения §5 на
прочность; найти то, что все восемь участников пропустили; вынести суждение по
предложению §6 и по вопросам §7 — что верно, что неверно, чего не хватает.
Особое внимание: §5.4 (роль версии) и §5.6 (механизмы против обязательств) —
это два разрешения, на которых держится всё остальное.

