У массовой автоматизации есть опасная иллюзия: если pipeline показал preview, получил approval и оставил audit log, то change якобы стал безопасным. Симптом заметен после incident: в log есть аккуратный record, reviewer помнит кнопку approve, а реальный scope оказался шире или состояние цели успело измениться. Цена ошибки — не только неверные объекты. Команда тратит часы на доказательство того, что именно произошло, и может повторить ошибку rollback-скриптом, которому доверили старый контекст.
Причина в смешении типов доказательств. Preview отвечает о предложении, authority — о допустимом пределе, approval — о согласии на связанный request, audit trail — о записанном событии, verification — о наблюдаемом результате. Ни один из них не выводится из другого автоматически. Технически зрелый процесс проверяет переход между карточками и умеет остановиться при любом разрыве. Он не называет «green pipeline» доказательством правильности, пока не названо, что конкретно было измерено.
Пять утверждений, которые нельзя склеивать
| Артефакт | Что можно утверждать честно | Типовая ложная интерпретация | Нужное следующее доказательство |
|---|---|---|---|
| preview | в фиксированный момент рассчитан proposed change для зафиксированного selection | реальный execute будет таким же | повторная проверка preconditions рядом с execute |
| authority | policy ограничила допустимый selector или размер scope | операция полезна и корректна | review содержимого preview и intent |
| approval | кто-то разрешил связанный request в своей роли | reviewer проверил каждую техническую деталь | binding id/digest и понятная review policy |
| audit trail | система зафиксировала событие по выбранному contract | событие необратимо доказано и state верный | свойства storage плюс independent verification |
| verification | авторитетный reader наблюдал заданное условие | все последствия change устранены | monitoring, residual-risk owner или новый rollback plan |
- Зафиксировать proposal. Сохранить operation id, selector, dense targets, digest и version, а не только красивый diff.
- Сравнить scope с authority. Проверить selector и limit до того, как появится approval request.
- Связать approval буквально. Operation, preview, target и authority ids должны совпасть без implicit default.
- Отделить receipt от evidence. После execute читать authoritative target и только затем решать, закрывать operation или готовить новый plan.
Preview — снимок намерения с известным сроком годности
Terraform отделяет speculative plan от apply: изменения target system между ранним plan и final apply могут изменить результат. Это относится и к самописному orchestration: состояние меняют другой job, человек или controller. Preview — вход в review, а не билет на исполнение через час.
Чтобы превратить preview в contract, сохраняют не только human-readable diff. Нужны stable operation id, model/config version, selector, dense target list, target digest, expected precondition и proposal digest. Digest — не мистическая защита: он просто позволяет проверять равенство того, что видел reviewer, и того, что получил executor. Надёжность hash, подписи и collision resistance — отдельные свойства конкретной реализации. В fixed model digest — статическая строка именно поэтому; fixture проверяет связь, а не криптографию.
Dry-run не обладает одной силой
Называть любой безопасный расчёт dry-run вредно. Kubernetes reference для kubectl apply различает client dry-run и server dry-run. Первый печатает object без отправки; второй отправляет server-side request, но не сохраняет ресурс. Это два разных наблюдения: один проверяет serialization и локальную обработку, другой может пройти часть server-side пути. Они полезны, но neither превращается в утверждение о будущем persistent change.
В инженерном process рядом с названием preview пишут source of truth и side-effect boundary. Например: «local model preview, no external read», «server admission dry-run, no persistence» или «read-only inventory with fixed snapshot». Тогда reviewer понимает, какие questions ещё открыты. Если source inventory старый, target list может быть уже неверен. Если server dry-run прошёл, policy может поменяться до execute. Если preview собран на client, он может не знать admission rule. Каждая граница добавляет самостоятельный check, а не больше уверенности в одном цветном статусе.
Authority ограничивает мощность, не доказывает смысл
Authority card в хорошем дизайне мала и машинно проверяема: level, allowed selector, maximum count, time window и исключения. Она не хранит длинное объяснение change и не решает за owner, стоит ли менять поле. Её задача — не позволить approval от малой операции «переехать» на больший scope. Если preview содержит три targets, а authority допускает два, executor не округляет limit и не просит пользователя «подтвердить ещё раз» в том же запуске. Он возвращает stop-scope-exceeds-authority-limit.
GitHub Docs для environments показывает похожую, но не тождественную механику: job ожидает protection rules, а секреты environment недоступны до их прохождения. Это gate доступа к стадии запуска. В документе есть и bypass boundary: возможность администратора обходить правила зависит от настройки environment. Следствие для собственного runner простое: bypass — тоже операция с отдельным identity, reason и audit event. Его нельзя скрыть внутри общего approved, иначе расследование не отличит нормальный путь от исключения.
import {
createFixedSyntheticOperationCard,
createFixedSyntheticAuthority,
previewSyntheticAutomation,
requestSyntheticApproval,
} from './upgrade-2025-05.mjs';
const preview = previewSyntheticAutomation(
createFixedSyntheticOperationCard('fixed-over-scope-v1'),
);
const gate = requestSyntheticApproval(
preview,
createFixedSyntheticAuthority('fixed-over-scope-v1'),
);
console.log({ accepted: gate.accepted, reason: gate.reason, targetCount: gate.targetCount });
// Expected: false, stop-scope-exceeds-authority-limit, 3.
// The values are fixed synthetic in memory; no target is read or changed.
Пример показывает не «проверку прав пользователя», а exact contract. Card с тремя synthetic targets и authority максимум на два закрывает путь до approval. Никакой тайный fallback не оставляет третью запись «на потом», никакой реальный job не создаётся. Для production нужны identity provider, policy owner, storage и real-time conditions; они намеренно не входят в fixture.
Approval обязан связывать четыре значения
Минимальный approval связывает operation id, preview digest, target digest и authority id. Если хотя бы одно значение другое, согласие не относится к request. Отдельно полезны policy version, reviewer role, expiry и reason. Но поля не делают approval автоматическим доказательством: reviewer мог видеть неполное представление, policy могла быть слишком широкой, а time window — слишком длинным. Поэтому в high-impact operations approval сопровождают change owner, expected verification и stop owner, а не только именем reviewer.
Target mismatch — показательная авария. Preview может содержать synthetic-targets-a-b-v1, approval — synthetic-targets-a-c-v1, а оба выглядят похожими для человека. В fixed fixture это не «warning»: approval получает stop-approval-does-not-bind-preview-target-and-authority. Хороший runner использует такую же fail-closed семантику для отсутствующего, неизвестного и forged field. Иначе позитивный путь testируется, а ошибочный request случайно получает полезный default.
Audit trail хранит причинную цепочку, но не наблюдает state
Audit trail нужен потому, что у команды несколько источников истины: queue, executor, policy engine и target system. Он связывает phase transition одним operation id и удобен для восстановления последовательности. Но audit record может сказать «simulation recorded» и не может сам прочитать target. Поэтому audit fields не заменяют verification fields. Если их смешать, dashboard будет показывать «verified» по факту наличия log, хотя реальные targets никто не открыл.
Verification и rollback не должны быть скрытым retry
Verification начинается с измеримого expected state. Не «проверьте всё», а «reader X видит field Y со значением Z на scope digest D; отсутствующие targets перечислены; stale data имеет отдельный reason». Если read не прошёл, state не равен expectation или источник не авторитетен, status остаётся stopped. Green execution receipt не переписывают как verification.
Rollback нужен именно в этот момент, но не как автоматическая инверсия исходного payload. Что вернуть зависит от observed state: часть targets могла измениться вручную, исходное значение уже неактуально, а побочные объекты требуют другого порядка. Поэтому rollback plan содержит preconditions: собрать observed state, создать новый bounded scope, получить новую authority, подготовить новый preview и approve отдельную операцию. «Rollback planned» не равно «rollback executed» и не даёт права делать новый write.
| Нарушение | Где обнаруживается | Результат | Почему это важно |
|---|---|---|---|
| unknown или missing field | preview contract | stop без proposal | не возникает default operation |
| forged authority | authority gate | stop before approval | нельзя расширить limit подменой card |
| approval с другим target digest | approval binding | stop before simulation | review не переносится на другой selection |
| sparse или cyclic structure | canonical comparison | reject без exception | fixture не скрывает некорректный input |
| forged audit record | verification entry | stop, не verified | record не становится state evidence |
Ограничения и следующий проверяемый шаг
В model нет реального permission check, scheduler, external time, inventory, API, storage или production target. Все values fixed synthetic и лежат в памяти. Поэтому fixture не проверяет network race, cryptographic digest, identity, доступность audit store или честность reviewer. Она проверяет более узкую вещь: каждый переход принимает только exact card и закрывается на mismatch, missing, unknown, sparse или cyclic input.
Следующий проверяемый шаг: возьмите самый короткий batch automation и перечислите для него пять предикатов из первой таблицы. Для каждого напишите owner и data source. Затем удалите один binding field — например target digest — и попробуйте доказать, что executor всё ещё не сможет применить старый approval к новому scope. Если доказательство строится на дисциплине оператора, а не на contract, добавьте gate до расширения автоматизации.
Историческая граница мая 2025
В этой статье Terraform v1.10.5 используется только для границы speculative plan, GitHub Docs commit от 31 января 2025 — только для behaviour environment protection rules и bypass setting, Kubernetes reference commit от 14 марта 2024 — только для различия client/server dry-run. Все ссылки закреплены immutable commits и существовали к маю 2025. Матрица authority, stop codes и fixed cards — собственный учебный механизм автора; они не названы гарантией сторонних систем.
Проверяемые источники
- HashiCorp Terraform plan, immutable commit 898e397 for v1.10.5 (22 January 2025) — Документ говорит, что plan сам не выполняет proposed changes; speculative plan описывает эффект без намерения применить его, а изменения target system между plan и apply могут изменить final effect, поэтому final non-speculative plan надо перепроверять. Граница: Документация Terraform не задаёт универсальный protocol approval, состав audit trail или rollback для произвольной автоматизации и не доказывает корректность конкретного change.
- GitHub Docs: managing environments for deployment, immutable commit 6a92295 (31 January 2025) — GitHub описывает, что job, ссылающийся на environment, не стартует и не получает environment secrets до прохождения protection rules; required reviewer может пропустить job, а bypass зависит от отдельной настройки environment. Граница: Это поведение GitHub Actions environments в описанной версии. Оно не подтверждает семантическую верность diff, полноту preview, независимость reviewer или безопасность другого CI и production процесса.
- Kubernetes kubectl apply reference, immutable website commit c889d9b (14 March 2024) — Справочник kubectl apply различает dry-run=client, который печатает object без отправки, и dry-run=server, который отправляет server-side request без persistence ресурса. Граница: Этот справочник не обещает, что последующий реальный apply увидит то же состояние, пройдёт те же policy или не затронет другие объекты. Он описывает только режим dry-run конкретной команды.