ADR-0002: контракт стабильности публичного API
- Статус: принято
- Дата: 2026-07-10
- Изменено: 2026-08-02 после принятия владельцами экспериментального scope-контракта инвентаря native-codegen
- Владельцы: скрипты, runtime, сборка и выпуск, документация
Контекст
FOnline предоставляет игровым проектам много поверхностей: CMake helpers, команды BuildTools, настройки, структуру пакетов, нативные hooks, методы, типы, события и свойства AngelScript, метаданные, сериализованные сущности, сетевые сообщения, форматы файлов и ABI приложений и runtime.
Доступность не равна обещанию совместимости. До принятия этого решения PUBLIC_API.md смешивал текущие, планируемые и устаревшие утверждения и не мог быть сгенерирован или сверен с исходным кодом. Теперь локализованный индекс публичных контрактов генерируется, фактическая классификация стабильности остаётся во владеющей модели, а корневой путь сохраняется как устойчивый legacy-маршрут. Замораживание каждого доступного символа также препятствовало бы необходимому рефакторингу движка.
Разработчикам и ИИ-агентам необходимо знать, какая поверхность стабильна, какая экспериментальна, где находится авторитетное объявление и какую работу требует изменение контракта.
Решение
Метки стабильности
Каждый генерируемый публичный контракт использует одну из следующих меток:
| Метка | Значение |
|---|---|
stable |
Поддерживается для документированных линий выпусков; несовместимые изменения требуют миграции и решения по выпуску. |
experimental |
Публично доступно для проверки, но сигнатура или поведение могут изменяться при наличии явной записи в примечаниях к выпуску. |
internal |
Доступная деталь реализации без обещания совместимости. |
deprecated |
Ранее поддерживаемая поверхность, запланированная к удалению; замена и срок удаления документированы. |
Пока символ или поверхность не классифицированы явно, они считаются internal. Существующая доступность, пример в проекте или присутствие в сгенерированных bindings не делают поверхность стабильной автоматически.
Источник истины
- Объявления в исходном коде, metadata annotations, парсеры, объявления настроек, CMake helpers и тесты авторитетны для текущего поведения.
- Генерируемые канонические модели контрактов нормализуют эти источники в стабильные идентификаторы и машиночитаемый JSON.
- Справочные страницы для людей, локализованные индексы публичных контрактов и корневой legacy-маршрут
PUBLIC_API.mdгенерируются из этих моделей и дополняются примерами и объяснениями, ориентированными на задачи. - Ручной текст не должен поддерживать количества объявлений, сигнатуры или междоменные реестры, которые можно сгенерировать.
Docs/generated/source-inventory.jsonостаётся независимым реестром исходников; восемнадцать моделей контрактов владеют соответствующими повторно используемыми поверхностями. - Документация проекта может описывать использование API движка, но не может повышать метку стабильности этого API.
Классификация, принадлежащая исходному коду
Символы нативной кодогенерации используют отдельный metadata tag ///@ ApiContract <selector> <label> .... Селектор сначала разрешается по каноническому сгенерированному id, затем по family_id, чтобы одно проверенное объявление могло охватывать семейство перегрузок. Зарезервированный селектор scope:native-codegen классифицирует всю текущую модель и требует пины SymbolCount и InventorySha256 для отсортированного инвентаря стабильных ID. Tag разбирается и проверяется BuildTools/codegen.py, но относится только к документации и исключён из runtime compatibility hash.
Без корректного scope-контракта неаннотированные символы остаются internal (default). Корректный scope-контракт применяет проверенную метку, только пока совпадают оба пина инвентаря; любое добавление или удаление символа либо изменение стабильного ID останавливает генерацию до обновления пинов владельцем. Точное объявление символа может переопределить scope, а пересекающиеся exact/family declarations остаются недопустимыми. Явный tag internal фиксирует проверенное решение, не повышая статус символа. Для stable и experimental требуется Since; для deprecated требуются версия объявления устаревшим, существующая замена и цель удаления.
Contract tags не заменяют проверку владельцем. Текущее scope-объявление классифицирует все 2 527 сгенерированных символов как привязанные к ревизии experimental с версии 2022.1.0.wip, а вспомогательный метод разработки Game.BreakIntoDebugger сохраняет явный статус internal. Это делает интеграционную поверхность пригодной для использования и отслеживания изменений, не заявляя широкой совместимости stable.
Домены контрактов
Модель стабильности применяется отдельно к следующим областям:
- build helpers, options, stages и BuildTools CLI;
- настройки и ключи конфигурации;
- методы скриптов, глобальные функции, типы, enums, события, remote calls и свойства;
- нативные hooks и ABI расширений;
- ABI хоста и runtime клиента и протокол updater;
- сериализованные данные, сущности базы данных и metadata миграций;
- сетевой протокол и версия совместимости;
- авторские и сгенерированные форматы файлов;
- структура пакетов и матрица поддерживаемых платформ.
Выпуск может поддерживать один домен, не обещая стабильность каждого домена.
Политика изменений
Несовместимое изменение поверхности stable требует:
- API diff или эквивалентное обнаружение, привязанное к исходникам;
- явную классификацию breaking change;
- инструкции по миграции и замену, когда она применима;
- решение для release notes и политики поддержки;
- обновление версии совместимости или metadata миграции, когда этого требует сетевой или сериализованный контракт;
- одновременное обновление сгенерированного справочника, примеров и тестов.
Скрытые fallback aliases, недокументированные compatibility shims и устаревшие дублирующие перегрузки не являются стандартной стратегией миграции. Слой совместимости добавляется только по требованию политики поддержки и получает владельца и условие удаления.
Для поверхности experimental breaking changes всё равно требуют явной записи в changelog или release notes, чтобы пользователи и ИИ-агенты могли выбрать правильную ревизию.
Автоматическая проверка генерируемых контрактов
BuildTools/docs_contract_diff.py сравнивает все восемнадцать моделируемых доменов с теми же путями на базовом SHA pull request или ревизии перед push: native API, CMake, основной BuildTools CLI, package, helper CLI, native extension, prototype, map, model, text, effect, image, particle, font, audio, video, GUI runtime и AiControl protocol. Специализированный нативный слой BuildTools/docs_api_diff.py сохраняет семантику символов и перегрузок; остальные модели сопоставляют принадлежащие исходникам стабильные идентификаторы. Все домены классифицируют добавления, документацию, политику и breaking changes относительно стабильности baseline, поэтому одновременное понижение до internal не может обойти проверку.
Для вспомогательных скриптов каждый исполняемый create_parser() остаётся источником истины синтаксиса, а BuildTools/HelperCliInterface.json владеет стабильной идентичностью helper, назначением, аудиторией, владельцем вызова и явными исключениями. Проверка AST inventory отклоняет новый открытый helper parser, который не смоделирован и не отнесён к другому каноническому домену. Домен helper CLI остаётся internal, пока владелец не утвердит версионированную политику поддержки.
Для нативного C++ проекта BuildTools/NativeExtensionInterface.json моделирует роли исходников, используемые текущими targets, поддерживаемые сигнатуры hooks, fallback, call sites и правила bindings. Структурные тесты и тесты генератора сопоставляют его с текущим поведением CMake/codegen; runtime input он не является. Домен имеет статус experimental и совместим по исходному коду только на закреплённой ревизии движка; он не обещает бинарный ABI между ревизиями. Проектные реализации и внешние SDK остаются вне контракта движка.
Breaking change объявления stable, experimental или deprecated требует точной записи схемы v2 в накопительном реестре Docs/contract-change-dispositions.json. Запись содержит домен, классификацию владельца, обоснование, миграцию, release note и обработку совместимости и привязана к change ID и baseline/current digest соответствующего домена. Изменения источника модели, области модели и контракта уровня модели также требуют решения. Изменения внутренних записей остаются видимыми в отчёте, но не становятся обещаниями совместимости.
CI записывает JSON/Markdown-отчёты для пары ревизий в Workspace/, проверяет отсутствие пропущенных решений и загружает отчёт даже при ошибке. Текущий сайт не публикует исторические снимки только ради этой проверки.
Версионирование
Документация описывает текущую ветку, пока у движка не появятся осмысленные теги выпусков и support matrix. Исторические снимки API не создаются только потому, что слой публикации умеет их размещать. После появления поддерживаемых линий выпусков сгенерированные модели и страницы закрепляются на неизменяемых тегах или ревизиях.
Последствия
Положительные
- Разработчики могут отличать контракт от реализации и примеров.
- Рефакторинг движка остаётся возможным, поскольку неклассифицированные внутренние поверхности не замораживаются случайно.
- Breaking changes становятся видимыми, проверяемыми и снабжёнными миграцией.
- Генерируемый JSON предоставляет инструментам документации и ИИ-системам стабильные идентификаторы символов.
- Проектные примеры не могут молча переопределять гарантии движка.
Издержки
- Существующие поверхности необходимо инвентаризировать и классифицировать до выдачи широких обещаний
stable. - Аннотации и описания в исходном коде требуют улучшения там, где сгенерированный справочник недостаточно содержателен.
- Междоменная автоматизация diff и её накопительный реестр решений требуют постоянного сопровождения по мере расширения доменов контрактов и политики выпусков.
- При изменении стабильного контракта сопровождающие должны писать руководство по миграции.
Отклонённые варианты
- Всё доступное является публичным и стабильным: отклонено, поскольку замораживает внутреннюю реализацию и противоречит текущему рефакторингу.
- Полное отсутствие обещаний стабильности: отклонено, поскольку разработчикам игр нужна надёжная поверхность интеграции.
- Ручные списки в
PUBLIC_API.md: отклонено, поскольку сигнатуры, количества и владение расходятся с исходным кодом. - Использование в проекте определяет стабильность: отклонено, поскольку зависимость одного проекта является свидетельством, а не гарантией движка для всех пользователей.
Проверка
BuildTools/docs_inventory.py --checkзакрепляет независимый inventory экспортов, тестов и настроек, полученный из исходного кода.BuildTools/docs_api.py --checkзакрепляет каноническую модель нативной кодогенерации, включая family/symbol IDs, описания, явное или default происхождение контракта, metadata жизненного цикла, сигнатуры и происхождение объявления.BuildTools/docs_reference.py --checkзакрепляет сгенерированные страницы для людей из модели нативной кодогенерации.- Фокусные тесты доказывают, что селекторы семейства
ApiContractохватывают перегрузки, устаревшие count и hash scope-контракта отклоняются, точные переопределения scope работают, замены deprecated разрешаются, некорректные селекторы отклоняются, а аннотации только для документации не изменяют compatibility hash. BuildTools/docs_contract_diff.py --enforce, специализированный API comparator и их фокусные тесты отклоняют removal, shape change и stability withdrawal публичных baseline-записей без решения, устаревшие digest и изменения контракта модели или parser во всех восемнадцати генерируемых доменах.BuildTools/docs_public_api.py --checkпересобирает корневой индекс контрактов из этих восемнадцати моделей и отклоняет устаревшие ссылки доменов, метки или количества нативных символов.- Широкая классификация
stableостаётся задачей владельцев и политики выпусков; закреплённый инвентарём scope со статусомexperimentalсам по себе не обещает стабильную совместимость между ревизиями. - Code review должен отклонять текст, который представляет неклассифицированный доступный символ как stable.