DarkRiDDeR14 мин

Полевой маршрут: автоматизация без массового неверного change

ИнфраструктураНадёжность

Полевой риск автоматизации выглядит буднично: небольшая операция накопила исключения, кто-то добавил 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 по умолчанию.

Карточка операции перед первым запуском
ПолеПример в учебной моделиПроверка владельцаСтоп, если
operationIdsynthetic-normalize-doc-labels-bounded-v1ID один и тот же в preview, approval, audit, verifyпереиспользован или отсутствует
targetSelectionsynthetic-doc-a + synthetic-doc-bids dense, selector и exclusions видныlist пустой, sparse или не совпадает с selector
targetDigestsynthetic-targets-a-b-v1approval связывает именно этот listdigest другой или не зафиксирован
authoritysynthetic-authority-l2, максимум два targetlimit и selector подходят операцииscope шире authority
expected verificationexternal 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?

Петля доказательств: preview и dry-run создают proposal, authority ограничивает scope, approval связывает карточку, simulated execute оставляет audit record, независимая verification сравнивает observed state. Красный выход STOP ведёт к новому rollback plan, а не к автоматическому обратному write.
Проверка результата намеренно находится после execute и возвращает процесс к новому plan. Так receipt об исполнении не выдаётся за наблюдение состояния, а rollback не наследует полномочия исходной операции.

Ограничьте 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», а недостающая проверка с владельцем и сроком.

Реакция на результат после execute
НаблюдениеСтатус процессаРазрешённое действиеЧто нельзя делать
observed state совпадает с expectationverified с named boundaryзакрыть card и сохранить evidenceобъявлять будущие runs безопасными
часть targets не совпалаstop / investigateсохранить exact subset и owner recoveryзапустить широкую обратную операцию
reader недоступен или evidence staleunknown / stopвернуть verification ownerподменить наблюдение exit code executor
scope неизвестен после partial failurestop 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

  1. Проверьте target contract. Selector, ids, digest, exclusions и max count можно показать в одной карточке.
  2. Проверьте freshness preview. Явно задайте, когда preview перестаёт быть применимым и должен быть пересчитан.
  3. Проверьте binding approval. Operation, preview, target и authority ids совпадают буквально, а не «примерно».
  4. Проверьте audit boundary. Log хранит links, но команда не называет его observed state и не заявляет свойства storage без доказательства.
  5. Проверьте observed verification. Назначены reader, expectation, timeout и владелец mismatch.
  6. Проверьте 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 конкретной команды.