Самый дорогой template обычно выглядит очень гибким. В него постепенно добавляют boolean-поля: включить другой runtime, не создавать metadata, применить альтернативный delivery, разрешить особую сеть. Каждое поле кажется безобидным, но комбинации создают продукт, который никто не поддерживает. Пользователь получает возможность собрать противоречивую конфигурацию, reviewer — обязанность восстановить намерение по анкете, а platform team — скрытый набор compatibility promises. Цена растёт быстрее числа шаблонов: она растёт числом комбинаций, для которых нет владельца и rollback.
Механика должна сделать невозможным хотя бы очевидную ошибку: нельзя провести неготовое решение через форму просто потому, что поле существует. Для этого template не принимает произвольный объект «настройки сервиса». Он принимает короткий закрытый contract: какие facts обязательны, какие значения образуют base path, какое расширение дозволено и по какому условию нужно прекратить генерацию. Такой contract не делает архитектуру автоматической. Он делает точку выбора наблюдаемой и проверяемой человеком.
Симптом → причина → проверка → действие
- Симптом. В форме появляются взаимно исключающие чекбоксы, а в generated repository остаются комментарии «выберите одно из двух позже».
- Причина. Input model открыт: поле добавляют для текущей просьбы, не связывая его с owner-ом, базовым инвариантом и дальнейшей поддержкой.
- Проверка. Перечислите допустимые keys и canonical combinations. Отдельно назовите values, которые ведут к golden path, к named extension и к отказу. Не используйте случайный user input как доказательство policy.
- Действие. Примите только закрытый input contract, пересоберите решение из versioned record, а всё, что не совпало, направьте в review или decline. Эта проверка может жить рядом с template, но не подменяет реальное выполнение.
Три разных механизма, которые часто путают
Repository template и platform template решают соседние, но разные задачи. GitHub template repository копирует структуру и файлы в новый repository; у созданных ветвей несвязанная история. Это хорошо для стартового состояния, но не означает обновление копий в будущем. Backstage Software Templates работают как scaffolder: template описывается как entity с owner, type, parameters и последовательными steps; skeleton и переменные можно использовать для создания компонента. Это способ провести согласованный маршрут, а не стандарт качества организации.
Третий слой — командный contract. Он не поставляется GitHub или Backstage. Именно он отвечает, какой service type повторяем, какие inputs считаются допустимыми, где проходит extension boundary и что делать при несовпадении. Без этого слоя портал лишь ускоряет копирование. С ним portal становится удобным интерфейсом к уже принятой policy. Если platform team меняет form, но не меняет contract, она меняет UI, а не архитектурное решение.
| Слой | Что он умеет | Чего он не доказывает | Кто владеет решением |
|---|---|---|---|
| GitHub repository template | создать новый repository со структурой и файлами | синхронизацию будущих копий и общую policy | owner template repository |
| Backstage Software Template | собрать parameters и выполнить последовательные scaffolder steps | корректность любого action или бизнес-совместимость output | owner template и owner интеграций |
| Base contract | назвать allowed input, invariant, output и refusal | результат CI, безопасность и adoption | platform team вместе с доменным owner-ом |
| Named extension | добавить одно описанное различие поверх base path | право заменить runtime, ownership или data policy | owner extension и reviewer |
| Design path | разобрать новый класс работ отдельно от формы | что новый class уже готов стать default | команда, которая владеет задачей |
Закрытый input contract
У формы должен быть не только список labels, но и точная форма данных. Для учебного internal service это marker synthetic, scope, mode fixed-memory-only и один из заранее записанных case id. В реальном template аналогом будет схема параметров и server-side validation, привязанная к версии contract. Важна идея: input не может содержать template path, repository path, usage counter, issue link, CI result или произвольный policy object только потому, что клиент отправил лишнее поле. Такие значения смешивают выбор с недостоверным evidence и дают инструменту слишком много полномочий.
Закрытость нужна не из любви к строгим схемам. Она связывает действие с известным источником правил. Когда service name, owner и approved class соответствуют base contract, результат может быть «golden path». Когда есть ровно одно заранее названное различие, результат может быть «extension review». Если record не существует, default должен быть decline. Иначе новая policy незаметно становится частью прошлой версии template, а платформа узнаёт об этом только после fork-а.
// Synthetic fixture: здесь caseId выбирает только один embedded record.
const input = createFixedSyntheticTemplateInput('fixed-reviewed-escape-hatch');
const report = inspectSyntheticTemplateChoice(input);
if (report.decision !== 'use-reviewed-extension') {
throw new Error('unexpected synthetic decision');
}
// report.extension содержит name, owner, boundary и rollback.
// Она не читает template.yaml, repositories, issues, usage или CI.
Base path и extension должны иметь разные права
Base path фиксирует то, что уже является повторяемым: например, internal HTTP service с known owner, approved runtime и internal data class. Он может выдать skeleton, draft metadata и checklist. Но он не должен принимать параметр «выбери любой runtime». Такой параметр не расширяет base path, а удаляет его смысл. Разница важна для rollback: если contract остаётся целым, команда возвращается к предыдущей versioned записи; если base invariants менялись произвольно, назад возвращать уже нечего.
Extension можно разрешить, только если он не меняет base invariants. Named observability adapter — хорошая учебная граница: он добавляет один adapter record, имеет owner и явно не заменяет runtime, ownership, data class и delivery policy. Другой retention model, неизвестный owner или новая external access policy — не extension. Они изменяют сам тип решения. Передавать их через extension значит назвать fork безопасным только потому, что он лежит в другом поле формы.
Проверка canonical record до draft
Фикстура в этом пакете намеренно не принимает произвольный request. Она хранит три fixed synthetic records в памяти: чистый golden path, одно именованное расширение и отказ от одноразовой регулируемой миграции. Input с неожиданным field, другим scope, режимом scan-real-template или неизвестным case id отклоняется. Это не parser настоящего template. Это маленькая модель того, почему платформа сначала должна подтвердить идентичность rule, а потом составлять proposal.
Вторая проверка защищает от более тонкой подмены. Plan принимает только report, который снова совпадает с canonical fixed record целиком: request, decision, extension и evidence. Если подменить decision на extension, убрать extension или добавить repositoryPath, draft не создаётся. После принятия plan содержит только draft actions и прямое описание границы: real template не читается и не пишется, files не читаются, CI не запускается, adoption и speed не измеряются. Это важно: хороший учебный fixture не выдаёт собственную согласованность за результат команды.
import {
createFixedSyntheticTemplateInput,
inspectSyntheticTemplateChoice,
planSyntheticTemplateDecision,
runPlatformTemplatesFixture,
} from './upgrade-2024-05.mjs';
const report = inspectSyntheticTemplateChoice(
createFixedSyntheticTemplateInput('fixed-reviewed-escape-hatch'),
);
const draft = planSyntheticTemplateDecision(report);
if (!Object.values(runPlatformTemplatesFixture().assertions).every(Boolean)) {
throw new Error('synthetic fixture failed');
}
console.log(report.decision); // use-reviewed-extension
console.log(draft.ci); // not-executed
// Фикстура использует только embedded records. Она не читает шаблон,
// репозиторий, файлы, usage, issue, интервью, CI или сеть.
node web/scripts/upgrade-2024-05.mjs --verify-fixture
# PASS подтверждает согласованность только fixed synthetic records и отрицательных веток.
Отказ — часть API, а не аварийная ветка
В техническом смысле decline должен иметь такой же ясный contract, как success. Для одноразовой regulated data migration input говорит: shape не повторяется, owner не известен, runtime не выбран, data class и retention model новые. Ответ «decline-template» не оставляет пользователя на пустой странице. Он возвращает reason, следующий human question и rollback: generated артефакта нет, остаётся только decision record. Дальше нужна маленькая design review, а не поиск ещё одного параметра.
Это повышает качество технического интерфейса. Пользователь знает, почему не получил генерацию. Reviewer знает, что нужно решить. Platform team не вынуждена поддерживать потенциально опасное исключение. Если через время сценарий станет регулярным, можно создать новый versioned contract с отдельным owner-ом и доказать границы реальными разрешёнными evidence. Пока этого нет, честный отказ лучше ложного зелёного результата.
Маршрут внедрения механики
- Записать base facts. Зафиксируйте target type, owner, mandatory inputs, outputs и list of non-goals. Версия contract меняется вместе с этими словами, а не только с template file.
- Сделать schema закрытой. Отвергайте неизвестные keys и неполные combinations. Нельзя передавать через форму path, credential, raw policy или метрику usage без отдельного разрешённого контекста.
- Развести три outcomes. Golden path использует base record; extension создаёт review draft; decline открывает design path. Никакой outcome не должен молча становиться fork.
- Записать rollback. Для base и extension proposal обозначьте, какой versioned draft можно убрать. Не обещайте откат реальных репозиториев, если их жизненный цикл не входит в scope.
- Подключить реальный action отдельно. После согласования policy проведите threat model, credential review, dry run, output validation и ограниченный rollout в своих системах. Fixture и documentation не выполняют это вместо команды.
Ограничения и следующий шаг
Backstage v1.25.0 показывает parameters и serial steps, но не объявляет все custom action безопасными или подходящими. GitHub template repository создаёт новые repository, но не создаёт канал обновления между независимыми историями. JSON schema или validation logic могут отфильтровать shape input, но не узнают бизнес-смысл нового data policy. Поэтому не надо называть closed contract «автоматической архитектурой». Он только удерживает инструмент в границах решений, которые команда уже владеет.
Следующий шаг — выбрать один действующий template и выписать его current inputs без изменения output. Затем пометить каждый input как base invariant, named extension или unowned choice. Любой третий тип сначала вынесите из формы в design queue. После этого можно добавить отрицательные tests: неизвестный field, изменение decision, подмена extension, попытка рассматривать report как plan. Это дешевле, чем обнаруживать несовместимые forks по истории репозиториев.
Историческая граница мая 2024
Все технические ссылки ограничены snapshot Backstage v1.25.0 и GitHub Enterprise Server 3.12, доступными к маю 2024. Они используются только для заявленных механизмов: parameters и steps scaffolder-а, создание repository из template и несвязанная история его ветвей. Из источников не выводятся claims об adoption, скорости разработки, безопасности action или качестве реального generated output.
Проверяемые источники
- Backstage v1.25.0: Software Templates, апрель 2024 — Первичный снимок документации Backstage, выпущенный до конца мая 2024. Software Templates умеют загрузить skeleton, подставить переменные и опубликовать результат; задача показывает шаги и поддерживает отмену, но документ не обещает скорость внедрения, корректность конкретного template или автоматическую синхронизацию с будущими изменениями.
- Backstage v1.25.0: Adding your own Templates, апрель 2024 — Первичная документация формата Template: owner, type, parameters и последовательные steps. Она показывает возможности scaffolder-а, но не определяет policy команды, допустимость всех action, безопасность секретов или критерий, когда от template нужно отказаться.
- GitHub Enterprise Server 3.12: Creating a template repository — Первичная историческая документация GitHub, доступная до мая 2024. Новый repository получает структуру и файлы template, однако ветви имеют несвязанную историю. Это объясняет риск самостоятельных копий; источник не описывает portal-template, ownership или процесс обновления команды.