G2-MECHANICS — механика эволюции формата: второй взгляд
01Это второй независимый разбор свода FINDINGS-DIGEST.md. Предмет — как наш
формат меняется во времени, не ломая читателя. Самый ценный результат здесь —
обоснованное несогласие; где я согласен, я добавляю того, чего в своде нет.
02Правило разметки источников. У меня нет интернета, поэтому:
[ИЗ СВОДА] — из FINDINGS-DIGEST.md; [ИЗ ДЕРЕВА] — проверено мной чтением
кода, с файл:строка; [ПО ПАМЯТИ, НЕ ПРОВЕРЕНО] — мои знания о чужих
системах, без выдуманных версий/дат/номеров.
TL;DR — где я не согласен
- 03Avro для нас — дороже, а не «почти бесплатно». Предпосылка свода «схема и данные в одной истории git» неверна: каталог лежит в реестре, схема (Rust-типы) — в исходном репозитории vibevm. Это две разные истории. Внешний читатель без репозитория и скопированная наружу запись ломают модель Avro целиком — остаётся терпимый читатель, который у нас уже есть.
- Объединение НЕ «угадывает по набору ключей». В текущем дереве вариант
Directoryуже несёт позитивный дискриминантkind:"directory"(crates/vibe-index/src/types/repomd.rs:44-50). Настоящий дефект —#[serde(untagged)], и он устраняется сменой одного атрибута. - «14 полей-коллекций» — заниженный периметр. С
skip_serializing_if = "...is_empty"в дереве 21 поле, не 14. Решение А6 принималось по числу 14; для языка схемы важны все 21. - Невосстановление присутствия у списков в protobuf — осознанное, а не «незавершённость». И нам для каталога оно не нужно: машинные поля не бывают legitimately «неизвестными».
- «Мы наследуем проблемы JSON-кодировки protobuf без их выхода» — неверно
по рамке. Наш выход — убрать
deny_unknown_fields(один атрибут), а не двоичный формат. Скалярного коллапса proto3 у нас в JSON нет вовсе. - Идентичность поля именем — компенсируется. Свод предложил список
отставленных имён; этого мало для rename-безопасности. Настоящий механизм —
#[serde(alias = …)], и он бесплатен в JSON и TOML.
0. Что я перепроверил по дереву (доверие своду — с поправками)
04Свод верен в конструкции, проверенной мной [ИЗ ДЕРЕВА]:
- 05
deny_unknown_fieldsв каталоге — ровно 15 вcrates/vibe-index/src(repomd.rs:1,entry/relations.rs:6,entry/mod.rs:1,entry/content.rs:5,entry/aggregate.rs:2). Совпало со сводом. #[serde(other)]— нигде в дереве (подтверждает иcampaigns/packages-2026-09/harvest/a6-wire-format-census.md:359-360). Совпало со сводом.- Версия каталога — ярлык, а не переключатель. Единственное сравнение
версии во всех
.rs:crates/vibe-core/src/manifest/lockfile.rs:430if lockfile.meta.schema_version != CURRENT_SCHEMA_VERSION— это lockfile. Каталог читается через голыйserde_json::from_slice(crates/vibe-index/src/index/repomd.rs:32-39), версия не сравнивается ни разу. Совпало со сводом. - Клиент терпим, себе строги.
crates/vibe-registry/src/index_client/wire.rs— узкие view-структуры (VersionEntryViewчитает толькоversion,wire.rs:30-33),#[serde(default)], безdeny_unknown_fields; комментарийwire.rs:37-39прямо говорит: «Extra fields on the wire … are tolerated silently — kept simple so a server-side envelope addition does not force a client bump». Совпало со сводом.
06Поправки к своду [ИЗ ДЕРЕВА]:
- 07Объединение описано неточно. Свод: «Общего поля-признака нет; читатель угадывает по набору ключей». В дереве:
08 // crates/vibe-index/src/types/repomd.rs:41-55
#[serde(untagged)]
pub enum RepomdFileEntry {
Directory {
/// Always the literal string `"directory"`. Carrying this as
/// a tag inside the directory variant lets serde's `untagged`
/// matcher distinguish unambiguously.
kind: DirectoryTag,
entries: u32,
},
File { size: u64, sha256: String },
}
09У Directory есть позитивный тег; «по набору ключей» угадывается только
File. Буква «общего поля нет» верна, но вывод «угадывание» — преуменьшение.
- 10«14» против «21». Полей с
skip_serializing_if = "...is_empty"вcrates/vibe-index/src— 21: Vec::is_empty— 12 (relations.rs:18,31,44,46,65,78;entry/mod.rs:72,78,96,108;content.rs:68,75);BTreeMap::is_empty— 2 (content.rs:39,41);*Entry::is_empty(структурные) — 7 (entry/mod.rs:87,90,93,99,102,105,111). Свод считает только сырые коллекции (12+2 = 14); но структурные поля вродеprovides: ProvidesEntryколлапсируют «пусто≡отсутствует» точно так же: когда структура пуста, поле пропускается (entry/mod.rs:90,ProvidesEntry::is_emptyвrelations.rs:35-39). Для языка схемы релевантны все 21. Периметр «сырые коллекции» оправдан, но число, на котором решали А6 (14), занижено относительно того, что схеме предстоит выразить.
11Не перепроверял и не считаю нагрузко-несущим для своих выводов: «23 типа / 18 каталожных / 86 полей». Принял из свода как есть.
1. Avro — насколько хороша для нас на самом деле? (ДЕШЕВЛЕ ИЛИ ДОРОЖЕ ТЕРПИМОГО ЧИТАТЕЛЯ)
Что должен сделать читатель, чтобы модель Avro сработала
12Модель Avro [ПО ПАМЯТИ, НЕ ПРОВЕРЕНО]: каждая запись пишется против схемы
писателя; читатель обязан добыть именно ту схему писателя, по которой
записано, и согласовать её со своей схемой читателя. Обычно — по отпечатку
(fingerprint), который лежит в данных, через реестр схем.
13Чтобы это работало у нас, читатель должен:
- 14Извлечь из данных идентификатор схемы писателя. У нас в
repomd.json—schema_version: u32(repomd.rs:21), не отпечаток схемы. То есть читатель знает «версия 1», но не знает, какой именно текст схемы соответствует версии 1. - Где-то этот текст найти. У нас его для каталога нет как артефакта —
[ИЗ ДЕРЕВА]: вcrates/vibe-index/нет.json-схемы; схема существует только как Rust-типы (types/) и спека-проза (PROP-005). - Запустить согласование схем.
Предпосылка свода неверна: это не «одна история git»
15Свод [ИЗ СВОДА]: «в git-репозитории это стоит указателя, и история схемы
лежит в той же истории, что и данные». Но:
- 16Каталог (
repomd.json,primary.jsonl,by-name/*.json) — это публикованный артефакт реестра; полеregistry/registry_url(repomd.rs:22-23) указывает на репозиторий-источник. Схема (Rust-типы) живёт в исходном репозитории vibevm. Это два разных репозитория с двумя разными историями. История каталога не содержит текста схемы — значит, «одна история» ложно. - «Схема едет с данными» — тоже ложно сегодня: рядом с
repomd.jsonнет файла схемы. (Контрпример из дерева: дляvibe tree --jsonсхема есть —crates/vibe-cli/resources/package-tree.schema.v1.jsonс$idиschema_version:{const:1}, плюс золотой тестcrates/vibe-cli/tests/tree_json.rs. Способность публиковать артефакт схемы в доме есть — просто не применена к каталогу. Это и есть настоящий Avro-смежный пробел.)
Читатель без репозитория / запись, скопированная наружу
- 17Внешний инструмент читает каталог из реестра; он не клонирует исходный репозиторий vibevm с Rust-типами. У него нет ни схемы писателя, ни пути к ней. Механика согласования Avro не запускается — он возвращается к «читать JSON как умею», то есть к терпимому читателю. Avro ему ничего не дал.
- Запись, скопированная из репозитория наружу (строка
primary.jsonlв баг-репорте, зеркало метаданных пакета): нет ни версии, ни контекста — чистый JSON. Avro невозможен; терпимое чтение — единственный вариант. А это самый реальный способ, которым нашими данными будут пользоваться.
Вердикт: ДОРОЖЕ
18Avro требует: публиковать машинно-читаемый артефакт схемы с данными (или
реестр), учить читателей его доставать и согласовывать, версионировать этот
артефакт, держать дисциплину отпечатков. Это большая машинерия — ради выгоды,
которую у нас некому потреблять (внешних читателей ноль [ИЗ СВОДА], а своим
согласование схем не нужно: писатель и читатель — один бинарник).
19Терпимый читатель требует: читать узкими view, игнорировать незнакомые поля,
отсутствующее трактовать как default. Это уже написано (wire.rs).
Маржинальная стоимость — около нуля.
20Ответ на вопрос: Avro для нас дороже терпимого читателя, заметно и по
машинерии, и по предпосылкам. Настоящий (и полезный) урок Avro — не «возить
схему с данными», а «сделать схему машинно-читаемым артефактом вообще». Этого
артефакта у каталога сегодня нет; фикс — опубликовать JSON Schema / JTD (паттерн
уже есть в vibe-cli/resources/), а не внедрять разрешение схем Avro.
2. «Пусто против отсутствует» — нужна ли нам различимость списков
Невосстановление у protobuf — осознанный отказ, а не недоделка
21Свод [ИЗ СВОДА]: для скаляров присутствие вернули через ~5 лет, для списков и
словарей — «так и не вернули … слиты навсегда», и подаёт это как незавершённость.
22Я утверждаю, что это осознанный отказ, и вот цена различимости для списка
[ПО ПАМЯТИ, НЕ ПРОВЕРЕНО]:
- 23каждое поле-список получает третье состояние (нет / пусто / заполнено);
- сгенерированный код несёт
Option<Vec>илиhas-бит на каждое такое поле; - каждый читатель обязан решить, что семантически значит «нет» против «пусто» — а это решение не универсально;
- любой инструмент, не понимающий присутствия, молча сколлапсирует их обратно (round-trip-несоответствие).
24Для proto3 цена особенно высока: их двоичный формат не выделяет присутствие для repeated-полей без явного бокового маркера, и реставрация ломала бы компактность и обратную совместимость с существующими данными. Отказ рационален.
Главный аргумент: для машинных данных «отсутствует» — не настоящее состояние
25Ключевое различие, которое свод не провёл: присутствие нужно человеко-написанным данным, где «я не заполнил» реально, и почти не нужно машинно-сгенерированным, где каждое поле детерминированно вычислено.
26Каталог пишет индексатор. Он всегда знает значение каждого поля — читает его из
vibe.toml пакета. Поэтому «отсутствует» для каталожного поля — не легитимное
«неизвестно», а всегда «вычислено как пустое». Различимость пусто/нет ничего
не покупает, потому что состояние «нет» никогда не наступает законно.
27Конкретно по нашим полям [ИЗ ДЕРЕВА]: authors, keywords, requires.packages,
provides.capabilities, features, i18n.available — все вычисляются
индексатором из манифеста; «нет» у них не бывает. Даже requires_any
(entry/mod.rs:96) — «зависимость any-of не задана» — это ограничение, а не
присутствие.
Где различимость всё-таки нужна — и это не каталог
28У манифеста vibe.toml, который пишет человек [ИЗ СВОДА], «я не указал» —
реальное состояние. Там authors = [] (явно: авторов нет) против отсутствующего
authors (не записано) — осмысленное различие. И TOML здесь помогает, а не
мешает: отсутствие null в TOML — это фича, она принуждает выражать
трёхзначность через «нет ключа» против [], без null-сигнала, который можно
перетолковать. JSON и TOML соглашаются в механизме.
Вердикт
29Различимость пусто/нет для списков нам для каталога НЕ нужна. 21 поле со
skip_serializing_if = "...is_empty" безопасны для машинных данных. Присутствие
стоит сохранять только там, где «неизвестно» легитимно — то есть в
человеко-написанном манифесте. И свод сам к этому приходит в рекомендации №2
(null не нужен: в TOML его нет) — но он не развёл, что для каталога это
безразлично, а для манифеста — обязательно, и фиксы там разные.
3. Тегированные объединения — что делать с RepomdFileEntry
30Текущая форма [ИЗ ДЕРЕВА] (repomd.rs:41-77): #[serde(untagged)],
Directory { kind: DirectoryTag, entries }, File { size, sha256 }. На проводе
{"kind":"directory","entries":N} против {"size":N,"sha256":"..."}. Дефект не
в «отсутствии тега», а в untagged: плохие ошибки («did not match any variant»),
асимметричная разметка (один вариант мечен, второй угадывается), и — главное для
А6 — untagged трудно/невозможно выразить в обычном языке схемы.
31Ниже — пять конкретных форм с ценой. A, B, D — рабочие; C — ценовой baseline (текущее направление); E — отбраковывается.
Форма A — явное поле-дискриминатор (стандарт)
32#[serde(tag = "kind", rename_all = "lowercase")]
pub enum RepomdFileEntry {
Directory { entries: u32 },
File { size: u64, sha256: String },
}
33{ "kind": "directory", "entries": 42 }
{ "kind": "file", "size": 184522, "sha256": "..." }
- 34Цена: каждое файловое действие дополнительно несёт
"kind":"file"(байты); ломающее изменение файлового варианта (сегодня у файла нетkind) — но внешних потребителей ноль[ИЗ СВОДА], сейчас это даром. - Чем платим / получаем: чистые ошибки serde, оба варианта симметричны,
форма выражается в JTD, JSON Schema (
discriminator/oneOf), protobuf (oneof), Avro (union). Это ровно то, что разрешает блокер А6 — тип становится выразимым в языке схемы в тот момент, когда мы перестаём братьuntagged. По сути — один атрибут от нынешнего состояния.
Форма B — разные ключи верхнего уровня (разделённые карты)
35"files": { "primary.jsonl": { "size": 184522, "sha256": "..." } },
"directories": { "by-name": { "entries": 42 } }
- 36Цена: одна логическая коллекция (
files: BTreeMap<…>,repomd.rs:34) дробится на две; читатель обязан проверять обе; теряется единый детерминированный порядок обхода путей; путь не может мигрировать файл↔каталог без переноса ключа. - Чем платим: фрагментация API и двойная бухгалтерия. Зато каждый кусок
однороден и строго типизирован. Урок PyPI
[ИЗ СВОДА](«поле, которое бывает bool или dict → крах») аргументирует за явную структуру — разделение на две типизированные карты структурно явно, но ломает инвариант «один путь → одна запись».
Форма D — вложение в именованный объект (обёртка) — нет в своде
37Соседне-тегированная форма, родная для serde:
38#[serde(tag = "kind", content = "value", rename_all = "lowercase")]
pub enum RepomdFileEntry {
Directory { entries: u32 },
File { size: u64, sha256: String },
}
39{ "kind": "directory", "value": { "entries": 42 } }
{ "kind": "file", "value": { "size": 184522, "sha256": "..." } }
40(Одно-ключевая форма {"directory":{…}} / {"file":{…}} тоже возможна, но
требует ручной ser/deser — serde не даёт её из коробки; adjacent-тег —
серде-нативный её аналог.)
- 41Цена: лишний уровень вложенности и
value-обёртка → больше байтов и глубины. - Чем платим / получаем: максимальная самодокументируемость (читатель видит
единственный ключ и знает вариант, не сканируя поля) и чистая
прямая-совместимость: третий вариант
{"kind":"symlink","value":{…}}добавляется без двусмысленности. Самая «будущестойкая» форма; переплата байтами оправдана, если вариантов со временем будет больше двух.
Форма C — вывод вида по обязательному полю (baseline = текущее направление)
42{ "entries": 42 }
{ "size": 184522, "sha256": "..." }
- 43Цена: это худшая форма — ровно то нетегированное угадывание, которому
свод (справедливо) не доверяет, и ровно форма PyPI «bool или dict», которая
роняла
pip[ИЗ СВОДА]. Плохие ошибки, двусмысленность при совпадении имён полей в будущих вариантах. - Чем платим: включена сюда только как ценовой baseline, чтобы оценить
остальные. По сути это нынешний дизайн минус тег у
Directory.
Форма E — тег в ключе карты — отбраковывается
44"dir:by-name": { "entries": 42 }
"primary.jsonl": { "size": 184522, "sha256": "..." }
- 45Цена: загрязняет пространство имён путей (путь — данные, не тег типа); путь, содержащий двоеточие, ломает схему; читатель парсит строки. Сильно не рекомендуется: путь — это идентичность и должен оставаться чистыми данными.
Рекомендация
46Форма A. Один атрибут от нынешнего состояния, стандарт во всех языках схем,
дешёвая, и — критически — устраняет блокер А6 («тип невыразим в языке схемы»):
он невыразим только пока мы держим untagged. Если ожидается рост числа
вариантов — Форма D.
4. Асимметричная строгость — граница: терпимо к чему именно
47Свод (рекомендация №4) делит строгость по поверхности: строго на входной двери
(vibe.toml, руками — ловим опечатку), терпимо дальше (каталог, машиной —
переживаем будущее). Это верно, но НЕ проработано по видам «незнакомого».
Разберу пять видов.
48Вид 1 — незнакомое ПОЛЕ (ключ). Терпимо = игнорировать. Это безопасный,
стандартный, прямо-совместимый выбор [ПО ПАМЯТИ, НЕ ПРОВЕРЕНО]. Цена: опечатка
в имени поля молча игнорируется (потеря данных этого поля). Парирование:
строгость на двери авторинга (валидация vibe.toml), где опечатки и рождаются.
→ Терпим дальше.
49Вид 2 — незнакомое ЗНАЧЕНИЕ словаря (известное поле, новый член enum,
kind:"plugin"). Спорное [ИЗ СВОДА]: Google — не ломающее, K8s/LinkedIn —
ломающее. Механическая причина [ИЗ СВОДА]: неизвестное значение обязано
лечь в типизированную ячейку, перечисляющую только известное; у нас без
#[serde(other)] разборщик падает. Самая опасная терпимость — из аварии
Cloudflare [ИЗ СВОДА]: старый читатель молча отображал неизвестное в default и
уверенно считал неверно» («ботовый балл ноль»). Молчаливый default
хуже отказа. → Граница: никогда не проглатывать неизвестное значение enum в
default. Либо отказ (строго), либо явный вариант Unknown(String), который
потребитель обязан обработать. Причём граница зависит от поля**: kind
примыкает к идентичности пакета — неизвестный kind = читатель не может даже
категоризировать, отказ правилен; delivery (content.rs:51-57,
eager/lazy-push/lazy-pull) — это подсказка, не идентичность, тут Unknown +
мягкая деградация оправданы. Рецепт LinkedIn [ИЗ СВОДА]: новое необязательное
поле с новым enum, старые символы поддерживаются бессрочно.
50Вид 3 — незнакомый ТИП значения (строка там, где число, "size":"184522").
Это не эволюция, а порча. Несоответствие типа = фундаментальное расхождение
схем; коэрцция («строка-как-число → число») — в точности патология «будь
либерален» из RFC 9413 [ИЗ СВОДА]: дефект закрепляется как стандарт де-факто.
→ Твёрдая черта: несоответствие типа = отказ. Терпимость сюда = проглатывание
мусора.
51Вид 4 — лишний ЭЛЕМЕНТ в списке. Два подслучая:
- 52(а) Список длиннее, чем ждёт читатель (аппаратный лимит у потребителя). Это
авария Cloudflare
[ИЗ СВОДА]: сгенерированный файл вырос вдвое, у развёрнутых потребителей был зашит предел → глобальный сбой. Урок: читатели не должны хардкодить потолок длины открытых списков. → Терпим длину (читаем все). - (б) Элемент неожиданного ТИПА в однородном списке (строка в
Vec<u64>). То же, что Вид 3. → Отказ.
53Вид 5 — незнакомый КЛЮЧ в закрытой карте. Для BTreeMap<String, X>
незнакомые ключи обычно безопасны (карты открыты по природе). Но если значения
типизированы/enum — к значениям применяется правило Вида 3. Наш
features: BTreeMap<String, Vec<String>> (content.rs:39) открыт по ключам и
строковый по значениям → полностью терпим.
Где терпимость перестаёт быть эволюцией
54В тот момент, когда она отображает нераспознанный вход в определённое значение, на котором потребитель потом действует (паттерн Cloudflare «score zero»). Молчаливый default неизвестного значения enum — единственная самая опасная терпимость. Добавление (новые поля, новая длина, новые ключи карты) — терпимо; нарушение типа — нет; новые значения enum — не молча в default.
Уточнение к рекомендациям свода
55«Строго на двери, терпимо дальше» — верно, но недоспецифицировано на enum'ах.
Я бы уточнил: дверь (vibe.toml) отвергает и неизвестные значения enum, и
опечатки; каталог (вниз по потоку) терпит незнакомые поля, но всё равно
не молча дефолтит неизвестные значения enum — несёт их как Unknown,
чтобы будущий читатель мог ими воспользоваться. Это более острая граница, чем
просто «терпимо дальше».
5. Что из механики protobuf нам НЕ подходит — и наследуем ли мы проблемы JSON
Что НЕ переносится
- 56Числа как идентичность поля — ядро protobuf. См. §6: у нас имена. Мы не наследуем их rename-безопасность, но она нам и не нужна при правильной дисциплине имён.
- Двоичный формат — нерелевантен: мы публикуем JSON для читаемости людьми и инструментами, компактность не покупаем.
- Драма с удалением
required[ИЗ СВОДА]: protobuf убралrequiredиз-за долго живущих типов через границы организаций. У нас один писатель и ноль внешних потребителей[ИЗ СВОДА]— обязательные поля (VersionEntryполон них,entry/mod.rs:43-121) нормальны и полезны. Урок «required— зло» нельзя тащить оптом: он настроен на их развёртывание (много организаций, типы на десятилетия). Я бы локализовал:requiredзол лишь когда читатели, которых ты не контролируешь, могут быть вынуждены синтезировать значение. Писателя контролируем мы. Другой случай. - Коллапс присутствия скаляров в proto3
[ИЗ СВОДА]: proto3 не различает «нет» и default для скаляров. В JSON этого нет: отсутствие ключа,0иnull— три разных состояния. Так что скалярного коллапса proto3 мы вообще не наследуем.
Наследуем ли мы «проблемы JSON-кодировки без выхода» — НЕ согласен с рамкой
57Свод [ИЗ СВОДА]: JSON-кодек protobuf по умолчанию отвергает незнакомые поля, а
на жалобы ответ — «используйте двоичную»; мы наследуем весь набор проблем их
JSON без их выхода.
58Я не согласен с рамкой. «JSON-проблема» protobuf в том, что их JSON строг, а
двоичный — терпим, и недовольных отсылают в двоичный. Нас никто не заставляет
быть строгими в JSON: serde позволяет выбрать deny_unknown_fields или нет.
Сейчас мы сами выбрали строгость в 15 местах своего читателя и терпимость —
в клиенте. Так что «выход» (терпимое чтение) тривиально доступен нам убиранием
одного атрибута — двоичный формат для выхода не нужен. Свод подаёт так, будто
двоичный protobuf — единственный выход из строгого JSON; для нас выход —
терпимый JSON, и он бесплатен.
59Что мы реально наследуем от JSON-без-схемы: не «проблемы protobuf», а проще —
в JSON нет схемы, поэтому дисциплина эволюции живёт целиком в головах +
чекере. Это и есть урок Cloudflare [ИЗ СВОДА]: ужесточить приём собственных
сгенерированных файлов так же, как пользовательский ввод. И — повторю из §1 —
артефакт схемы для каталога у нас отсутствует, хотя способность его публиковать
в доме есть (vibe-cli/resources/package-tree.schema.v1.json).
6. Номера полей против имён — что теряем и возмещаемо ли
60У protobuf идентичность поля — число; у нас — имя (JSON/TOML ключ). Урок
protobuf [ПО ПАМЯТИ, НЕ ПРОВЕРЕНО]: переименование Rust-поля безопасно (число
остаётся), переименование НЕ является изменением провода.
61Что мы теряем, используя имена:
- 62Rename-безопасность. У нас переименование поля (
author→authors) — это изменение провода: старые читатели ищут «author» и не находят «authors». - Имена утекают в идентичность: на «более удачное имя» возникает ломающее искушение, которого числа бы не дали (обратная сторона: имена самодокументируемы).
63Возмещаемо ли в JSON и TOML — да:
- 64Список отставленных имён (рекомендация свода №6) — необходим, но недостаточен: он предотвращает коллизию, но не даёт rename-безопасности, лишь делает переименования явно-ломающими-и-отслеживаемыми.
- Алиасы при десериализации — настоящий механизм, которого свод не назвал:
65 #[serde(alias = "author")]
pub authors: Vec<String>,
66читатель принимает и «author», и «authors», обратно пишет «authors». Это и есть rename-безопасность, дёшево, в чистом JSON. Цена: алиас живёт в читателе (или пока все старые данные не мигрированы); писатель выпускает только новое имя; round-trip нормализует. Для каталога, переписываемого при каждом индексировании, миграция автоматична (следующая сборка пишет новые имена).
- 67В TOML алиас работает так же — мы читаем через
toml::from_str(как вcrates/vibe-index/src/lockfile.rs:48), serde-атрибуты едины для форматов. Так что компенсация одинакова для JSON и TOML; специфика TOML — не идентичность поля (ключи — те же строки), а отсутствие null (см. §2).
68Вердикт: мы теряем rename-безопасность и непрозрачную идентичность; полностью
возмещаемо через #[serde(alias)] + список отставленных имён + автоматическое
переиндексирование. Числа нам не нужны. Числа побеждают только в высокочурном
сетевом протоколе со многими независимыми командами; мы — читаемый человеком
формат данных в покое, здесь имена + алиасы лучше.
С чем я согласен — и что добавляю
- 69Терпимый по умолчанию каталог прав (клиент уже таков). Добавляю: каталог
прямо-совместим ровно благодаря терпимому читателю — поэтому собственный
строгий читатель сервера (15
deny_unknown_fields) — аномалия, и именно она делает так, что новый сервер не запустится на каталоге, записанном ещё более новым (repomd.rs:15+index/repomd.rs:38). - Строго на двери, терпимо дальше — согласен; уточнил границу по enum'ам
(§4: reject или
Unknown, никогда молча в default). - Контракт версии (старшая — отказ, младшая — предупреждение, отсутствие —
1.0) — согласен. Добавляю: сегодня версия каталога не сравнивается ни разу
(
index/repomd.rs:32-39), то есть у нас ярлык версии без контракта; пробел — в отсутствии самого сравнения, а не в форме контракта. - Каждое объединение — тегированное — согласен (§3, Форма A).
- Не переиспользовать имена, держать список отставленных — согласен; добавил алиасы как недостающий механизм rename-безопасности (§6).
Итоговый вердикт
70Из шести пунктов: в трёх (Avro, списки-присутствие, JSON-наследство protobuf) я
меняю вывод свода; в одном (объединение) поправляю факт и предлагаю пять форм; в
двух (строгость, номера полей) уточняю и добавляю. Ни одного пункта не принял
без добавления. Центральная конструктивная находка поверх свода: способность
публиковать машинно-читаемый артефакт схемы и версионировать его по пути уже
существует в этом репозитории (package-tree.schema.v1.json + jsonschema +
золотой тест) — просто не применена к каталогу; многие «теоретические» ответы
свода здесь уже имеют рабочий образец для копирования.