DarkRiDDeR13 мин

Автоматизация инженерной рутины без массовой ошибки

ИнфраструктураПроцессы

Рутина становится опасной в момент, когда один корректный шаг превращают в массовый. Скрипт умеет переименовать label, обновить правило или закрыть устаревший флаг на одном объекте — и тем же циклом затрагивает тысячу. Симптом обычно спокойный: список targets собран, команда выглядит идемпотентной, а change уже нельзя глазами просмотреть. Цена ошибки конкретна: массово неверное состояние, длинное восстановление, потерянное время reviewers и спор, какой именно запуск изменил объект.

Причина не в том, что автоматизация сама по себе плоха. Ошибка появляется, когда preview считают доказательством будущего результата, approval — доказательством правильности, а audit log — страховкой от последствий. Это три разных вопроса: что предлагается, кто разрешил ограниченную операцию, и что зафиксировал процесс. Между ними остаются selection, момент запуска, реальные права, состояние цели и проверка после change. Поэтому процесс нужно строить как цепочку доказательств, а не как одну зелёную кнопку.

Симптом → причина → проверка → действие

  1. Симптом. Один job готов обработать широкий selector, но reviewer видит только краткое «N объектов» и общий статус success.
  2. Причина. Команда не связала target list, preview, authority, approval и audit record одним неизменяемым идентификатором операции.
  3. Проверка. До запуска сравнить exact target digest и limit authority; после запуска запросить независимый observed-state check, а не только exit code job.
  4. Действие. Разделить путь на preview → bounded scope → approval → execute → audit trail → verification; rollback оформить отдельной операцией с новым preview и approval.

Preview показывает намерение, а не будущую реальность

Preview полезен, когда отвечает на короткий вопрос: «какие именно объекты и какое изменение мы сейчас предлагаем?» В Terraform документ для версии 1.10.5 прямо отделяет plan от применения: plan сам не выполняет change. Speculative plan может разойтись с final effect, если target system изменится между расчётом и execute.

Для массовой операции preview должен иметь собственный contract. В нём не «обновить документацию», а operation id, selector id, dense list target ids, digest списка, желаемый переход и граница времени. Если target list строится из широкого query, сохраняют и query, и результат query. Иначе reviewer согласует название операции, а исполнитель потом получает уже другой набор. Preview без списка — это заметка; preview без digest — это заметка, которую нельзя связать с approval.

Что фиксирует каждый барьер
БарьерВопросМинимальный артефактЧего он не доказывает
preview / dry-runчто предлагается сейчас?operation id, selector, target ids, digest, proposed diffчто реальный change позже увидит то же состояние
bounded scopeсколько и какие targets разрешены?max targets, allowed selector, exclusionsчто изменение семантически корректно
approvalкто согласовал именно этот scope?approval id, preview digest, authority id, decisionчто reviewer прочитал каждый объект или что execution пройдёт
audit trailкакой transition зафиксирован?append-only record или event с linksчто event невозможно подделать без свойств конкретного хранилища
verificationкакое состояние наблюдается после change?independent read, expected/observed comparison, stop reasonчто следующий change безопасен или rollback не нужен
Схема из семи карточек: preview, ограниченный scope, authority, approval, execute, audit trail, verification. Красная ветка ведёт к безопасной остановке и отдельному rollback plan; стрелки показывают, что approval и audit record не заменяют проверку результата.
Цепочка не пытается сделать один барьер универсальным. Preview связывают с targets, approval — с preview, а verification остаётся отдельным действием после исполнения.

Dry-run и preview различаются, но оба имеют границу

Термины часто смешивают. Preview может быть расчётом в доменной модели: список того, что процесс намерен сделать. Dry-run — режим конкретного инструмента. В справочнике Kubernetes зафиксированы два разных dry-run режима для kubectl apply: client печатает object без отправки, server отправляет request без persistence ресурса. Значит, даже внутри одной команды «проверили заранее» имеет разные источники данных и разные границы. Ни один режим не говорит, что поздний реальный request обязательно увидит тот же набор policy и состояние.

Ограниченный scope должен быть машинно проверяемым

Ограничение scope не означает «попросили быть осторожнее». Это правило, которое сравнивает authority с exact selection. У authority есть level, допустимый selector и максимум targets. У preview есть target digest. Approval содержит оба значения. Перед execute исполнитель ещё раз проверяет, что approval привязан к тому же operation id, selector, target digest и authority id. Любое несовпадение закрывает путь. Лучше получить стоп из-за лишнего target, чем позволить динамическому selector незаметно расширить команду.

GitHub Actions environments дают знакомый, но узкий пример такой границы: job, ссылающийся на environment, ждёт прохождения protection rules и не получает environment secrets до этого. Required reviewer может пропустить job, а возможность bypass зависит от отдельной настройки. Из документации не следует, что reviewer оценил бизнес-смысл diff или что выбранный environment равен вашему. Но она хорошо показывает разделение обязанностей: контроль допуска к запуску существует отдельно от содержательной проверки change.

import {
  createFixedSyntheticOperationCard,
  createFixedSyntheticAuthority,
  createFixedSyntheticApproval,
  previewSyntheticAutomation,
  requestSyntheticApproval,
  approveSyntheticOperation,
  simulateSyntheticExecution,
  verifySyntheticExecution,
  stopAndPlanSyntheticRollback,
  runEngineeringAutomationFixture,
} from './upgrade-2025-05.mjs';

const caseId = 'fixed-bounded-approved-v1';
const preview = previewSyntheticAutomation(createFixedSyntheticOperationCard(caseId));
const request = requestSyntheticApproval(preview, createFixedSyntheticAuthority(caseId));
const approved = approveSyntheticOperation(request, createFixedSyntheticApproval(caseId));
const receipt = simulateSyntheticExecution(approved);
const verification = verifySyntheticExecution(receipt);
const stop = stopAndPlanSyntheticRollback(verification);

if (!Object.values(runEngineeringAutomationFixture().assertions).every(Boolean)) {
  throw new Error('fixed synthetic fixture failed');
}

console.log({ preview: preview.status, execute: receipt.status, verify: verification.status, rollback: stop.rollbackPlan.status });

// Fixed synthetic values in memory only.
// No files, Git, network, CI, clock, telemetry, production, cloud, real API or real change is used.
// simulateSyntheticExecution records a model receipt; it never executes an external operation.

Пример выше не запускает command и не моделирует реальное окружение. Все cards, ids, digest, authority, approval, timestamps и outputs — fixed synthetic values в памяти. Функция с названием simulateSyntheticExecution создаёт только immutable-in-memory receipt с явным effect: no-system-change. Это важнее красивого demo: читатель может повторить negative cases без риска получить частично изменённую систему.

Approval — это binding, а не печать «безопасно»

Approval имеет техническую работу: связать согласие с конкретным preview. Для этого недостаточно поля approved: true. Нужны operation id, target digest, authority id, decision, version policy и понятный срок действия. Если approval не несёт digest, его можно случайно применить к другому списку targets. Если не несёт authority, непонятно, какому уровню риска соответствовало решение. Если не несёт identifier preview, reviewer не может доказать, что видел именно тот diff.

Audit trail нужен для расследования, не для оправдания запуска

Audit record записывает связь между intent и событием: operation id, preview digest, target digest, authority, approval, time, actor class, execution result и verification result. Он нужен, чтобы после сбоя не восстанавливать историю из чата и stdout. Но наличие log не делает change обратимым. Лог может быть неполным, неверно коррелированным или лежать рядом с системой, которая отказала. Свойства «append-only», signature и retention нужно доказывать свойствами конкретного storage, а не словом immutable в JSON.

Verification начинает новый вопрос

После execute не нужно спрашивать «job зелёный?». Нужно сформулировать expectation и observed evidence: какие fields на каком наборе targets должны измениться, кто читает их из authoritative source, как распознаётся partial change и сколько времени допустимо ждать. Если verification не получена или не совпала с expectation, процесс переходит в stop path. Он не делает второй автоматический write в надежде, что это станет rollback.

Rollback — отдельная операция, а не кнопка «вернуть». Перед обратным change нужно знать фактическое состояние, источник истины, новый scope и побочные зависимости. Поэтому безопасный stop может создать только rollback plan: зафиксировать reason, собрать observed state вне модели, получить новую authority, подготовить отдельный preview и approval. Эта задержка дешевле, чем обратный массовый change по старому предположению.

Маршрут внедрения на одной рутине

  1. Выберите одну обратимую операцию. Например, поменять synthetic label на двух учебных cards, а не «автоматизировать всё сопровождение».
  2. Опишите target contract. Dense ids, selector, exclusions, target digest, expected before/after и max scope.
  3. Разведите preview и execute. Preview не имеет capability писать; execute принимает только exact bound approval.
  4. Сделайте один refusal test. Подмените target digest или добавьте третий target. Ожидаемый результат — stop before approval.
  5. Добавьте observed verification. Заранее опишите, какую независимую read-проверку выполнит owner после реального запуска.
  6. Спланируйте rollback отдельно. Не выдавайте старый preview за разрешение на обратный change; создайте новый operation card.

Ограничения и следующий проверяемый шаг

Эта статья не даёт production runner, identity system, policy engine, durable audit storage или реальный rollback. Fixed model не читает и не пишет files, Git, сеть, CI, telemetry, cloud, production или real API. Она не подтверждает права пользователя, фактическое состояние targets, качество query или успешность внешнего execute. Её смысл уже: сделать несоответствие scope, authority или approval наблюдаемой причиной остановки.

Следующий проверяемый шаг: выберите один существующий batch-job и выпишите два списка — что он предлагает и что он на самом деле меняет. Затем добавьте в тест подмену target digest и отдельный assertion, что executor останавливается до write. Если пока нельзя назвать exact targets и owner verification, автоматизацию не расширяют: сначала строят contract preview и stop path.

Историческая граница мая 2025

Terraform commit для v1.10.5 датирован 22 января 2025; GitHub Docs commit — 31 января 2025; Kubernetes website commit — 14 марта 2024. Все источники существовали к маю 2025 и закреплены immutable commits. Они используются только для узких фактов о plan, environments и dry-run. Цепочка cards, fixed fixture, правила binding и rollback plan — инженерная конструкция автора, не обещание поведения Terraform, GitHub Actions или Kubernetes вне их документации.

Проверяемые источники

  • 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 конкретной команды.