Перейти к основному содержимому

ADR 0071 — Симметричная публикация по ступеням и корень, который предлагает выбор

  • Статус: принят
  • Дата: 2026-08-14
  • Расширяет: ADR 0034

Контекст​

ADR 0034 установил, что каждая постоянная ветка публикует собственную документацию. Несимметричным он оставил адрес: main публиковала в корень (/brabo/), и только у qa и dev был суффикс (/brabo/qa/, /brabo/dev/).

Асимметрия взимала три цены, все оплачиваемые читателем:

  1. Корень не говорил, что существуют другие ступени. Три сайта не видели друг друга: попавший на /brabo/ не мог добраться до /qa/, а попавший на /dev/ из Google — до noIndex — не знал, что читает непроверенную версию.
  2. Особый случай заражал механизм. Публикация в корень с удалением содержимого унесла бы /dev/ и /qa/ вместе с ним, поэтому у workflow был шаг, который выполнялся только на main, чтобы вернуть два других внутрь дерева. Путь, отличный от двух других, — это путь, который задействуется в трети случаев.
  3. Негде было объявить зрелость. "Стабильная", "кандидат" и "в разработке" были знанием тех, кто уже знал.

Решение​

1. Три ступени в /brabo/<branch>/​

main, qa и dev публикуются симметрично. Особый случай main исчезает, а вместе с ним условный шаг workflow.

2. Корень — генерируемый индекс, а не перенаправление​

Перенаправить /brabo/ на /brabo/main/ было бы проще и отвечало бы меньше: вопрос, который заставляет смотреть больше одной ступени, — "есть ли уже в qa то, что мне нужно?", а перенаправление проглатывает этот вопрос.

scripts/docs/landing.mjs генерирует страницу с тремя ступенями, меткой зрелости каждой и проставленной версией, прочитанной из тегов самого репозитория (vX.Y.Z для main, vX.Y.Z-<estagio>.N для остальных — формат scripts/ci/tag-release.ts).

Она предлагает то, что существует в дереве, а не фиксированный список: отсутствующая ступень не становится ссылкой, потому что ссылка, дающая 404, хуже отсутствия. И она выходит из индекса поиска с canonical, указывающим на /main/, — корень является индексом, а не содержимым, и, будучи проиндексированным, конкурировал бы с настоящей документацией.

Перенос main сломал бы каждую уже сохранённую глубокую ссылку: /brabo/architecture перестала бы существовать. GitHub Pages отдаёт 404.html корня для неизвестного пути, поэтому он перенаправляет /brabo/<algo> на /brabo/main/<algo>.

Есть защита, которая не является деталью: 404 внутри ступени (страница, которой действительно нет) не может быть перенаправлен снова, иначе браузер зацикливается. Поэтому тест защиты существует.

4. Ступень объявляется, а не выводится из пути​

Это самое важное изменение, и оно невидимо.

Сайт узнавал, продакшен ли он, сравнивая BASE_URL === '/brabo/'. Когда все три в подкаталоге, это сравнение становится ложным и для main — и следствием был бы noIndex: true в настоящей документации: она молча исчезла бы из Google, с зелёным CI, потому что ничто в сборке не проваливается из-за недоиндексации.

DOCS_BRANCH сопровождает DOCS_BASE_URL, а E_PRODUCAO становится DOCS_BRANCH === 'main'. Выводить окружение из строки пути — это тот вид связанности, который проявляется только при изменении пути.

5. Дерево собирается для всех трёх​

Шаг, существовавший только на main, становится единственным путём: две другие ступени берутся из текущей gh-pages, ступень push — из новой сборки, корень генерируется, и публикуется всё вместе с keep_files: false.

Не keep_files: true: тот никогда ничего не удаляет, и страница, удалённая из репозитория, осталась бы опубликованной навсегда. Сборка сохраняет то, что должно остаться, и удаляет то, что не должно, — включая, бесплатно, миграцию: файлы, которые main оставила в корне в старой раскладке, отсутствуют в собранном дереве, поэтому первый push их уносит. Ручной очистки gh-pages не требуется.

6. Переход засевает /main/ из старого корня​

В старой раскладке gh-pages:main не существует. Без обработки первый push dev или qa оставил бы /brabo/main/ несуществующим — а поскольку 404.html перенаправляет как раз туда, каждая старая ссылка указывала бы на 404 до следующего продвижения до main, которое может занять дни.

Опубликованный сегодня корень — это буквально сборка main. Поэтому он засевает /main/ при первой сборке, и сайт никогда не остаётся без стабильной документации. При следующей публикации main нормальный путь берёт управление, и этот блок больше не выполняется.

7. Переключатель использует абсолютный href​

Ссылка пересекает сайты с разным baseUrl. Относительная разрешилась бы внутри собственной ступени — /brabo/dev/main/, которой не существует.

Последствия​

  • About репозитория по-прежнему указывает на корень, который теперь является индексом. Никакого изменения конфигурации не понадобилось — и хорошо, что не понадобилось: ни один токен CI не имеет области администрирования репозитория (BRABO_BOT_TOKEN — это repo + workflow), так что автоматизация этого потребовала бы новых учётных данных.
  • /brabo/ перестаёт быть документацией и становится лишним кликом для того, кому нужна только стабильная. Это цена зафиксированного здесь выбора: показывать все три.
  • README.md и apps/web/src/routes/AuthLayout.tsx по-прежнему указывают на корень, который является точкой входа.
  • Публикация остаётся прямым push в gh-pages — третьим исключением из единственной двери PR, уже задокументированным в политике веток. Этот ADR зависит от этого больше, чем 0034: сборка всего дерева — ровно то, чего actions/deploy-pages не умеет.

Отброшенные альтернативы​

  • Перенаправить корень на /main/: проще и отвечает меньше (пункт 2).
  • Оставить main в корне и суффикс только для qa/dev: это текущее состояние, и оно сохраняет особый случай, задействуемый в трети случаев.
  • Версионирование Docusaurus (docs:version): другая ось. Ступень конвейера — это зрелость ТОГО ЖЕ содержимого; версия документации — замороженное содержимое. CONTRIBUTING.md уже фиксирует, что в 0.x версионирование добавляет сопровождение, ничего не давая взамен.
  • Публиковать main в корне И в /main/: сохранило бы старые ссылки без 404.html ценой дублирования всего сайта при каждой публикации и наличия двух индексируемых адресов с одинаковым содержимым.