В учебной очереди лежат три почти одинаковые просьбы: «нужен новый сервис». Первая действительно похожа на остальные: internal HTTP service, owner известен, runtime из approved набора, данные internal. Во второй нужен заранее названный observability adapter. В третьей команда хочет провести одноразовую регулируемую миграцию, но owner, runtime, retention и access model ещё выбираются. Ошибка — обработать все три одной кнопкой Create. Цена такой экономии появляется позже: третья задача получает service skeleton вместо решения, а первая и вторая начинают жить рядом с fork-ами, которыми никто не владеет.
Это не рассказ о конкретном repository или компании. Records ниже заранее записаны в памяти и помечены synthetic. Мы не читали template, usage, issue, interview, CI, файлы или сеть; не измеряли adoption, скорость и эффект в production. Цель разбора — показать, как М7-автор отделяет форму запроса от доказательства и как объясняет отказ без театра «платформа всё решила».
Три заявки, три разных результата
| Synthetic заявка | Наблюдаемый факт | Решение | Почему не fork |
|---|---|---|---|
| fixed-golden-path-fit | повторяемый service type, owner известен, approved runtime и internal data | use-golden-path | base contract уже описывает этот класс без новых policy |
| fixed-reviewed-escape-hatch | всё как в base path, плюс named observability adapter | use-reviewed-extension | одно различие имеет owner, границу и rollback; base invariants не меняются |
| fixed-decline-template | one-off regulated migration, owner и lifecycle не определены, нужны новая access и retention policy | decline-template | это design question, а не пропущенная checkbox в service form |
| локальный patch после генерации | нет versioned extension и неясно, кто поддерживает отличие | не является outcome fixture | patch скрывает контракт; он не доказывает допустимость решения |
Первый запрос: не усложнять то, что уже совпало
У первого record все факты совпадают с base contract. Это не повод сказать «сервис готов». Это повод применить короткий договор: service type известен, owner есть, runtime одобрен, data class internal. Result называется use-golden-path, потому что его inputs не требуют нового решения. Platform template может подготовить skeleton, metadata draft и review checklist. Дальше люди всё равно проверяют безопасность, credentials, delivery и поведение уже в пределах реальной системы.
Симптом здесь был бы другой: reviewer предлагает добавить option «может быть внешний data class, но пока оставим internal». Причина — желание удержать текущую задачу внутри одной формы. Проверка проста: меняет ли option base invariant? Если да, это не улучшение golden path. Действие — удалить option из base form и направить новую policy в отдельный путь. Первый запрос не должен платить complexity tax за гипотетическую третью задачу.
Второй запрос: extension должен быть конкретнее желания
Во втором record нужен observability adapter. Важна не вывеска «расширение», а его рамка: extension имеет identifier, owner, boundary и rollback. Он добавляет только adapter record, не переопределяет runtime, ownership, data class или delivery policy. Поэтому fixture выдаёт use-reviewed-extension, а план — только draft actions: записать base contract, открыть review на extension, зафиксировать owner, boundary и removal condition. Никакого реального action, template edit или CI запуск не происходит.
Такое различение защищает от ложного компромисса. Если пользователю нужна новая retention policy, нельзя назвать её observability adapter. Если extension просит заменить approved runtime, это уже изменение base contract либо новый component type. В обоих случаях reviewer останавливает форму. Нельзя выдавать общий namespace extension за свободный вход для любого domain solution.
// Данный фрагмент работает только с embedded synthetic case.
const report = inspectSyntheticTemplateChoice(
createFixedSyntheticTemplateInput('fixed-reviewed-escape-hatch'),
);
console.log(report.decision); // use-reviewed-extension
console.log(report.extension.id); // observability-adapter
console.log(report.evidence.ci); // not-executed
// Ни template file, ни repository, ни usage не читаются.
Третий запрос: form не обязана принять всё
Третья запись нарочно неудобна. Это one-off regulated data migration; владелец ещё не назначен, runtime не выбран, data class external and regulated, а access и retention модель только формулируются. Попытка прогнать её через internal service template создаст красивую структуру, но не ответит ни на один существенный вопрос. Более того, сама structure будет давить на решение: команда начнёт подгонять policy под generated файлы, а не наоборот.
Поэтому verdict decline-template содержит не только отказ, но и следующий вопрос: провести небольшой design review за пределами template и вернуться, только когда появится repeatable contract. Rollback здесь почти пустой: нет generated artifact, только synthetic decline record. Это хороший признак. Чем меньше неготовая задача успела создать, тем меньше полей придётся спасать при уточнении policy. «Не делали» иногда гораздо более обратимо, чем «создали и потом передумали».
Симптом → причина → проверка → действие на одной карточке
- Симптом. Команда просит добавить «универсальный» флаг, потому что текущая задача не помещается в service template.
- Причина. Различие затрагивает ownership, runtime, data class, retention или access policy; это меняет base invariant, а не только technical adapter.
- Проверка. Сверьте request с contract. Совпадение всех base facts даёт golden path. Одно заранее описанное дополнение с owner и rollback даёт extension review. Всё остальное требует decline.
- Действие. Не создавайте локальную копию и не расширяйте форму. Зафиксируйте reason, начните design path, а позже верните только устойчивый class как новый versioned contract или named extension.
Почему fork не является четвёртым разрешённым ответом
Fork часто маскируют словом «bootstrap». Пользователь создаёт repository из template, меняет структуру и обещает позже перенести полезное обратно. Но механизм GitHub template repository даёт новой ветви несвязанную историю. Это означает, что исходный template не получает естественный канал обновить все созданные copies. Такое состояние допустимо, когда команда осознанно владеет самостоятельным проектом. Оно не должно быть невидимым fallback для всякого несовпадения с form.
Полевое правило короткое: если отличие нельзя описать как named extension с owner, boundary и rollback, не называйте его extension. Если команда всё же продолжает отдельно, создайте design record, назначьте owner и скажите, что это отдельный path. Тогда платформа не обещает синхронизацию, которой у неё нет, а команда не получает ложное чувство, что она всё ещё находится на golden path.
Маленькая фикстура как проверка границы
Fixture этого sidecar хранит три records и использует закрытый input: synthetic marker, scope, fixed-memory-only mode и case id. Он отвергает extra field, который похож на request path или files, другой scope, scan mode и неизвестный case. Это намеренно. В реальной работе нельзя подменять policy случайной строкой из issue или коэффициентом usage, если контекст не разрешил читать их и если не определено, как они влияют на decision.
После inspection plan повторно сравнивает report с canonical embedded record. Подмена use-golden-path на use-reviewed-extension, исчезновение extension, добавление repositoryPath, циклический report или разрежённый список действий приводят к отказу без падения fixture. После этого rollback принимает только canonical synthetic draft. Такая модель не решает problem реального onboarding. Она показывает минимальную дисциплину: evidence, decision и operation не следует смешивать в одном неаудируемом объекте.
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 и отрицательных веток.
Маршрут для реальной команды после учебного разбора
- Возьмите одну настоящую заявку с разрешением на анализ. Не запускайте массовый scan. Выпишите только заявленный component type, owner и policy, которых она касается.
- Сопоставьте с текущим contract. Пройдите четыре base facts и один refusal criterion. Если данное поле не имеет owner-а или ожидаемого значения, это не input golden path.
- Сделайте выбор видимым. Golden path, extension review и decline должны попадать в разные human-readable records. Не прячьте decision в комментарии generated файла.
- Проверьте безопасный rollout отдельно. Для реального action нужны отдельные credentials, dry run, threat model, output validation, CI и rollback plan. Ни один из этих результатов не следует брать из fixture.
- Обновляйте policy после evidence. Повторяемость доказывается разрешёнными наблюдениями и review, а не количеством похожих копий в репозиториях. Только затем меняйте versioned template contract.
Ограничения, rollback и следующий шаг
Этот разбор не назначает owner реальной миграции, не выбирает data policy, не читает current template и не знает поддерживаемые runtime в чужой организации. Backstage documentation показывает, как описывать Template entity и sequential steps; GitHub docs — как создаётся repository из template. Ни один источник не говорит, что данная regulated migration можно автоматизировать или что extension безопасен. Именно поэтому synthetic report остаётся учебным record, а не рекомендацией выполнить действие.
Rollback лучше планировать до кнопки Create. Для base proposal можно удалить draft. Для extension proposal — вернуть base contract и закрыть review. Для declined request не надо «откатывать» ничего, потому что форма не создала ложный результат. Следующий шаг — добавить к одному реальному template явный refusal criterion и owner для каждого existing extension. Затем провести review двух самых частых локальных patches: возможно, один станет честным extension, а второй останется отдельным design path.
Историческая граница мая 2024
Разбор опирается только на первичные документы Backstage v1.25.0 и GitHub Enterprise Server 3.12, доступные до мая 2024. Из них взяты ограниченные факты о skeleton/parameters/steps и о новых repository с несвязанной историей. Все request records, decision codes и outcomes в статье synthetic; они не являются статистикой использования, интервью, issue-анализом, CI или production-результатом.
Проверяемые источники
- 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 или процесс обновления команды.