Полевой риск автоматизации выглядит буднично: небольшая операция накопила исключения, кто-то добавил selector «для удобства», а запуск теперь меняет всё, что совпало с шаблоном. Сначала это экономит час ручной работы. Затем неверный filter или stale inventory создаёт массовый change, который трудно отмотать. Цена ошибки — частично изменённые targets, потерянные данные о прежнем состоянии, простой команды и ещё один рискованный запуск ради исправления первого.
В этот момент нельзя ограничиться просьбой «давайте внимательнее ревьюить скрипт». Причина системная: у процесса нет наблюдаемой границы между расчётом, согласием, исполнением и проверкой. Нужен короткий маршрут, который может остановить работу раньше внешнего write: preview/dry-run, bounded scope, authority, approval, execute, audit trail, independent verification и отдельно подготовленный rollback plan. Каждая остановка должна возвращать причину и владельца, а не расплывчатый failure.
Сначала опишите operation card
Operation card — это не тикет с заголовком, а минимальный контракт запуска. В нём есть operation id, intent, selector id, dense target list, target digest, expected before/after, version change logic, requested authority и expected verification. Карточка должна уместиться в review без поиска по нескольким системам. Если один из fields нельзя назвать, это не повод проставить unknown и идти дальше. Это сигнал остановиться до того, как автоматизация выберет targets по умолчанию.
| Поле | Пример в учебной модели | Проверка владельца | Стоп, если |
|---|---|---|---|
| operationId | synthetic-normalize-doc-labels-bounded-v1 | ID один и тот же в preview, approval, audit, verify | переиспользован или отсутствует |
| targetSelection | synthetic-doc-a + synthetic-doc-b | ids dense, selector и exclusions видны | list пустой, sparse или не совпадает с selector |
| targetDigest | synthetic-targets-a-b-v1 | approval связывает именно этот list | digest другой или не зафиксирован |
| authority | synthetic-authority-l2, максимум два target | limit и selector подходят операции | scope шире authority |
| expected verification | external observed state после execute | есть reader и критерий success/failure | проверка только «job завершился» |
Preview и dry-run располагают данные, но не дают разрешение
Начните с preview, который можно показать reviewer без побочных эффектов. Он показывает proposed change и selection. Если инструмент предоставляет dry-run, подпишите его режим и источник. Kubernetes documentation различает client mode, который только печатает object, и server mode, который отправляет request без persistence. Но precondition всё равно проверяют перед execute. На реальном запуске другой controller может уже поменять target, а policy может принять другое решение.
Terraform описывает тот же интервал на другом языке: speculative plan не имеет намерения применяться, а изменения target system между ранним plan и final apply способны поменять effect. Поэтому не сохраняйте preview как статичный артефакт без срока. Укажите snapshot version, retrieval time, max age и condition, при котором runner обязан recompute preview. Даже если ваша система не похожа на Terraform, вопрос один: что делает старый calculated diff непригодным для нового execute?
Ограничьте scope до approval
Следующий шаг — проверить, может ли authority покрыть exact selection. Хороший authority level отвечает на три вопроса: какой selector допустим, сколько targets максимум разрешено и какие исключения требуют отдельного процесса. Это легче автоматизировать, чем оценку «change выглядит разумно». Если limit два, card с тремя targets должна остановиться до approval. Не уменьшайте список молча и не заменяйте limit на warning: оба решения делают следующее состояние непредсказуемым для reviewer.
Authority также не равна identity. В учебной модели synthetic-authority-l2 — строка и max targets, не пользователь и не реальная permission. В production policy владелец определяет identity source, expiry, delegated approval, break-glass и audit scope. GitHub environments дают понятный пример одного gate: job ждёт protection rules и не получает environment secrets до их прохождения. Это не общая модель полномочий, но полезное напоминание, что доступ к исполнению и смысловая правильность change — разные проверки.
Approval связывает review с конкретной карточкой
В approval должен попасть не только комментарий reviewer. Минимальный набор: approval id, operation id, target digest, preview digest, authority id, decision и срок. Тогда executor проверяет связку перед execute. Если approval относится к synthetic-targets-a-c-v1, а preview — к synthetic-targets-a-b-v1, путь закрывается. В нормальном UX это может быть короткое объяснение «target mismatch, создай новый preview», а не попытка догадаться, какой target имел в виду reviewer.
import {
createFixedSyntheticOperationCard,
createFixedSyntheticAuthority,
previewSyntheticAutomation,
requestSyntheticApproval,
} from './upgrade-2025-05.mjs';
const card = createFixedSyntheticOperationCard('fixed-lower-authority-v1');
const preview = previewSyntheticAutomation(card);
const authority = createFixedSyntheticAuthority('fixed-lower-authority-v1');
const result = requestSyntheticApproval(preview, authority);
console.log({ stopped: result.stopped, reason: result.reason, limit: result.authorityMaxTargets });
// Expected: true, stop-scope-exceeds-authority-limit, 1.
// This is fixed synthetic in-memory data, not a real approval or execution.
Этот компактный example проверяет failure path, а не счастливый сценарий. Card содержит два synthetic target, authority допускает один, поэтому approval request даже не формируется. Fixture дополнительно подаёт unknown/missing fields, forged authority и approval, mismatch targets, sparse arrays и cyclic structures. Все такие inputs fail closed: report содержит reason и effect: no-system-change, а не best-effort default.
Execute, audit trail и verification — три разные фазы
Только после exact approval executor может начать реальный change. До него полезно повторить preconditions: target ещё существует, version не устарела, scope не расширился, authorization не истекла. Если проверка не проходит, результат — stop, а не частичное применение «того, что осталось». Для high-impact операций стоит заранее решить, допустима ли partial execution, и кто владеет её recovery. Это domain decision, не побочный эффект loop.
Audit trail получает event после попытки execution, но event не подтверждает observed state. В нём полезно хранить operation id, input digests, authority/approval links, executor version, timestamps, per-target result и correlation id. Но свойства сохранности зависит от storage. Fixed script создаёт frozen record в памяти только для того, чтобы fixture могла проверить exact transition. Он прямо называет границу: это не durable append-only storage, не подпись и не доказательство, что target изменился.
Verification читает authoritative source вне executor и сравнивает observed state с explicit expectation. У неё три исхода: matched, mismatched или unknown. Только первый позволяет закрыть операцию с ограничениями; второй и третий идут в stop path. Убедительная фраза «apply completed» не должна менять status verification. Если reader недоступен, result не «успех с warning», а недостающая проверка с владельцем и сроком.
| Наблюдение | Статус процесса | Разрешённое действие | Что нельзя делать |
|---|---|---|---|
| observed state совпадает с expectation | verified с named boundary | закрыть card и сохранить evidence | объявлять будущие runs безопасными |
| часть targets не совпала | stop / investigate | сохранить exact subset и owner recovery | запустить широкую обратную операцию |
| reader недоступен или evidence stale | unknown / stop | вернуть verification owner | подменить наблюдение exit code executor |
| scope неизвестен после partial failure | stop before rollback | собрать inventory и новый preview | использовать исходный approval для rollback |
Rollback plan — новый change, а не команда в finally
Когда verification не пройдена, хочется сразу выполнить inverse. Это особенно рискованно после массовой операции: часть targets может быть исправлена руками, прежнее значение могло стать опасным, а новая policy может запрещать обратный переход. Поэтому rollback plan сначала фиксирует boundary: какой subset наблюдался, что именно неизвестно, кому принадлежит recovery, какие data нужны до следующего decision. План не выполняет write и не наследует approval исходной операции.
Rollback повторяет весь маршрут с новым scope, authority, preview, approval и verification. В fixed model stopAndPlanSyntheticRollback возвращает planned-only-not-executed: stop event не разрешает новый write.
Минимальный выпускной checklist
- Проверьте target contract. Selector, ids, digest, exclusions и max count можно показать в одной карточке.
- Проверьте freshness preview. Явно задайте, когда preview перестаёт быть применимым и должен быть пересчитан.
- Проверьте binding approval. Operation, preview, target и authority ids совпадают буквально, а не «примерно».
- Проверьте audit boundary. Log хранит links, но команда не называет его observed state и не заявляет свойства storage без доказательства.
- Проверьте observed verification. Назначены reader, expectation, timeout и владелец mismatch.
- Проверьте stop path. Target mismatch, отсутствующий field и unknown authority останавливают process до write; rollback остаётся отдельным plan.
Ограничения и следующий проверяемый шаг
Полевой маршрут не заменяет policy и review конкретной организации. Все data, timestamps, approval и audit records в fixture synthetic и fixed; никаких файлов, Git, CI, сети, telemetry, production, cloud или real API script не касается. Он не доказывает, что текущий batch-job идемпотентен, что audit storage неизменяем или что rollback возможен. Он даёт строго проверяемую форму остановки до внешнего change.
Следующий проверяемый шаг: выберите одну задачу с batch selector и создайте рядом с ней учебный test из трёх cases — valid bounded selection, over-scope selection и approval с другим target digest. Ожидаемый результат во втором и третьем case должен быть один: no execute, явный stop code, сохранённый preview. Только после этого подключайте реальный reader для verification; если этот reader ещё не определён, у операции нет доказательства результата.
Историческая граница мая 2025
Источники закреплены до мая 2025: Terraform v1.10.5 commit от 22 января 2025, GitHub Docs commit от 31 января 2025 и Kubernetes website commit от 14 марта 2024. Terraform подтверждает ограничение speculative plan, GitHub Docs — gate environment protection rules и configurable bypass, Kubernetes — различие режимов dry-run. Они не обещают единый безопасный процесс. Все stop codes, contracts и fixture этой статьи принадлежат fixed synthetic модели автора.
Проверяемые источники
- 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 конкретной команды.