8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 94,
|
||
"slug": "editorial-2025-05-field-engineering-automation",
|
||
"title": "Как ограничить batch-автоматизацию до безопасного изменения",
|
||
"excerpt": "Надёжный маршрут для автоматической операции: сначала получить точный preview, затем проверить scope и согласование, выполнить change, независимо проверить результат и остановиться перед опасным rollback.",
|
||
"contentHtml": "<p>Скрипт выбирает записи по фильтру и обновляет их за секунды. Затем владелец видит лишние изменения: шаблон совпал с архивными объектами, список targets устарел, а часть полей уже исправил другой процесс. Ошибка не заканчивается неудачным exit code. Она оставляет частичный change, теряет исходные значения и заставляет команду запускать ещё одну операцию для исправления первой. Цена ошибки — простой, ручная сверка и риск испортить данные при поспешном rollback.</p>\n<p>Безопасная batch-автоматизация строится как цепочка независимых границ: preview, ограниченный scope, проверка полномочий, approval, execute, audit trail и независимая verification. Ни один этап не должен выдавать результат следующего этапа. Preview не равен разрешению. Успешное завершение процесса не доказывает состояние данных. Rollback не должен автоматически наследовать полномочия исходной операции.</p>\n<h2>Сначала зафиксируйте контракт операции</h2>\n<p>До запуска опишите operation card — короткую карточку изменения. Она отвечает на вопрос: что именно процесс собирается изменить и как владелец узнает, что изменение завершилось правильно. В карточке нужны стабильный operation id, selector, полный список targets, исключения, digest списка, ожидаемое состояние до и после, версия логики и критерий проверки.</p>\n<p>Список targets должен быть плотным: без пропущенных элементов, неявного «всё найденное» и повторного поиска между preview и execute. Digest не заменяет список для чтения. Он связывает карточку, согласование и запуск. Если selector, список и digest невозможно показать вместе, reviewer не видит границу операции.</p>\n<table><caption>Поля карточки перед запуском</caption><thead><tr><th>Поле</th><th>Пример учебного значения</th><th>Проверка</th><th>Остановиться, если</th></tr></thead><tbody><tr><td>operationId</td><td>normalize-labels-2025-05-01</td><td>одно значение проходит через preview, approval и audit</td><td>идентификатор переиспользован или отсутствует</td></tr><tr><td>targets</td><td>doc-a, doc-b</td><td>список плотный, видны selector и exclusions</td><td>список пуст, разрежен или не соответствует фильтру</td></tr><tr><td>targetDigest</td><td>sha256:7b…</td><td>digest вычислен по каноническому списку</td><td>digest относится к другому набору</td></tr><tr><td>authority</td><td>label-editor, максимум 2 записи</td><td>лимит покрывает точный scope</td><td>операция шире разрешённого лимита</td></tr><tr><td>expectation</td><td>label=normalized после запуска</td><td>есть источник, который это прочитает</td><td>успехом считается только exit code</td></tr></tbody></table>\n<h2>Почему dry-run не делает запуск безопасным</h2>\n<p>Preview показывает proposed change без внешней записи. Это полезная граница для чтения, но не гарантия будущего результата. Между расчётом и запуском другой процесс может изменить target. Запись может исчезнуть. Политика может истечь. Поэтому preview получает время расчёта, версию входных данных и максимальный срок действия.</p>\n<p>Короткий пример ниже учебный. Он не обращается к файлам, сети или реальным targets. Его задача — показать отрицательный путь: authority разрешает одну запись, а preview содержит две. В таком случае код не уменьшает список молча и не запускает разрешённую часть.</p>\n<pre><code>const preview = {\n operationId: 'normalize-labels-2025-05-01',\n targets: ['doc-a', 'doc-b'],\n targetDigest: 'sha256:7b-demo',\n expiresAt: '2025-05-01T12:00:00Z'\n};\n\nconst authority = {\n selector: 'document-label',\n maxTargets: 1\n};\n\nfunction approve(preview, authority, now) {\n if (new Date(preview.expiresAt) <= now) {\n return { approved: false, reason: 'preview-expired' };\n }\n if (preview.targets.length > authority.maxTargets) {\n return { approved: false, reason: 'scope-exceeds-authority-limit' };\n }\n return { approved: true };\n}\n\nconsole.log(approve(preview, authority, new Date('2025-05-01T11:00:00Z')));\n// Учебный результат: approved=false, scope-exceeds-authority-limit.</code></pre>\n<p>Проверка должна происходить до approval и тем более до write. Нельзя превращать ограничение в предупреждение. Предупреждение оставляет решение в голове оператора и делает два одинаковых запуска разными по поведению. Явный stop code даёт владельцу причину, которую можно проверить и обработать.</p>\n<figure><img src=\"/assets/editorial/2025/engineering-automation-2025-dry-run-evidence-loop.svg\" alt=\"Схема безопасной автоматизации: preview проходит проверку scope и approval, затем execute, audit и независимую verification; при ошибке процесс останавливается и возвращается к новому плану\"><figcaption>Preview и approval ограничивают внешнее действие. Проверка результата находится после execute, а rollback начинается с нового плана.</figcaption></figure>\n<h2>Свяжите approval с тем, что увидел reviewer</h2>\n<p>Approval должен относиться к конкретной карточке, а не к названию задачи. Минимальная связка содержит operation id, preview digest, target digest, authority id, решение, identity reviewer и срок действия. Executor сверяет значения буквально. Если reviewer согласовал список doc-a и doc-b, а перед запуском список стал doc-a и doc-c, старое согласование недействительно.</p>\n<p>Это правило закрывает распространённый отрицательный путь. Сервис обновил inventory, получил новый список и продолжил старым approval. В логах есть «approved», но approval относился к другому scope. Правильное действие — остановиться, построить новый preview и запросить новое решение. Автоматически угадывать намерение reviewer нельзя.</p>\n<h2>Разделите execute, audit и verification</h2>\n<p>Execute сообщает, что процесс попытался применить change. Audit trail сохраняет связанный контекст: operation id, digest входа, версию исполнителя, время, результат по каждому target и correlation id. Audit не доказывает, что данные приняли новое состояние. Для этого нужен отдельный reader, который обращается к authoritative source после execute.</p>\n<p>Verification сравнивает наблюдаемое состояние с явным expectation. У неё должно быть не два, а три результата: matched, mismatched и unknown. Недоступный reader даёт unknown. Таймаут не превращает unknown в успех. Если один target совпал, а второй нет, операция остаётся остановленной с указанием subset и владельца восстановления.</p>\n<table><caption>Диагностика batch-операции</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>В preview слишком много записей</td><td>Selector шире ожидаемого</td><td>Сравнить selector, exclusions и лимит authority</td><td>Остановиться и сузить scope; не обрезать список автоматически</td></tr><tr><td>Approval есть, но digest не совпал</td><td>Inventory изменился после review</td><td>Сверить operation id и два target digest</td><td>Построить новый preview и получить новое approval</td></tr><tr><td>Executor завершился успешно, данных нет</td><td>Exit code приняли за наблюдаемое состояние</td><td>Прочитать authoritative source отдельным reader</td><td>Отметить unknown и назначить владельца verification</td></tr><tr><td>Изменена только часть targets</td><td>Partial execution или внешний конфликт</td><td>Сохранить точный subset и результаты по объектам</td><td>Остановиться; не запускать широкий inverse</td></tr><tr><td>Preview старше допустимого окна</td><td>Stale input или истёкшая политика</td><td>Сравнить retrieval time с max age</td><td>Пересчитать preview перед новым approval</td></tr></tbody></table>\n<h2>Rollback — отдельная операция</h2>\n<p>После mismatched verification естественно вызвать обратную команду в блоке finally. Это опасно. Часть объектов могла измениться вручную. Старое значение могло стать неверным. Новый selector может выбрать больше записей. Поэтому rollback сначала создаёт план восстановления: наблюдаемый subset, известные и неизвестные состояния, владельца recovery и данные для следующего решения.</p>\n<p>Дальше rollback проходит тот же маршрут: новый scope, новый authority check, новый preview, новое approval и новая verification. Исходное согласование не даёт права на обратную запись. Если команда пока не может перечислить targets для rollback, результатом должен быть stop и расследование, а не команда «вернуть всё назад».</p>\n<h2>Порядок действий</h2>\n<ol><li>Опишите operation card: intent, selector, targets, exclusions, digest и expectation.</li><li>Сформируйте preview без внешней записи. Зафиксируйте версию входных данных и срок действия.</li><li>Проверьте, что точный scope укладывается в authority. При превышении верните явный stop code.</li><li>Передайте reviewer карточку, preview и ограничения. Сохраните approval с привязкой к digest.</li><li>Непосредственно перед execute повторите проверки freshness, scope, identity и срока approval.</li><li>Запишите audit event с результатом каждой попытки и correlation id.</li><li>Прочитайте authoritative source отдельным reader. Закройте операцию только при matched.</li><li>При unknown или mismatched составьте новый rollback plan. Не используйте старый approval для обратного change.</li></ol>\n<h2>Ограничения</h2>\n<p>Этот маршрут не делает опасную операцию безопасной сам по себе. Он не доказывает идемпотентность кода, неизменяемость журнала, корректность identity provider, отсутствие гонок и возможность восстановления данных. Внешний сервис может вернуть неполный список, а reader — устаревшее состояние. Эти свойства нужно проверять в архитектуре конкретной системы.</p>\n<p>Учебный код использует фиксированные строки и память процесса. Он не является реализацией permission system, durable audit storage или реального batch runner. В production нужно определить источник targets, каноническое вычисление digest, правила истечения preview, владельца остановки и допустимость partial execution. Если хотя бы один из этих пунктов неизвестен, scope автоматизации следует уменьшить.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Операция готова к ограниченному запуску, если команда может воспроизвести три случая на тестовых данных: корректный bounded scope проходит весь маршрут; scope больше authority останавливается до approval; изменившийся target digest останавливает запуск после повторной проверки. Для каждого случая видны operation id, причина остановки или matched verification, запись audit и отсутствие запрещённой записи. Критерий не обещает результат для production. Он показывает, что границы процесса работают.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://developer.hashicorp.com/terraform/cli/commands/plan\" target=\"_blank\" rel=\"noopener noreferrer\">Terraform: команда plan</a> — документация описывает speculative plan как описание предполагаемого эффекта без намерения применять его.</li><li><a href=\"https://kubernetes.io/docs/reference/kubectl/conventions/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: kubectl usage conventions</a> — документация различает client-side preview и server-side dry-run.</li><li><a href=\"https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: deployments and environments</a> — официальное описание environment protection rules и дополнительных gates.</li></ul>"
|
||
}
|