Files
progcode/editorial/agent-rewrites/096.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
21 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 96,
"slug": "editorial-2025-05-practice-engineering-automation",
"title": "Массовая автоматизация без слепого запуска: scope, approval и проверка результата",
"excerpt": "Как превратить массовую инженерную операцию в проверяемую цепочку: зафиксировать targets, ограничить scope, связать approval с preview, отделить receipt от verification и остановиться при несовпадении.",
"contentHtml": "<p>Скрипт меняет label на двух объектах и отрабатывает за секунду. Через неделю тот же selector находит две тысячи объектов. Job завершается со статусом <code>success</code>, но один шаблон не подходит части targets. В журнале есть время запуска и имя оператора, а список фактически изменённых объектов восстановить нельзя.</p><p>Симптом тихий: preview показывает только число объектов, approval хранит <code>approved: true</code>, а исполнитель повторно вычисляет динамический selector во время запуска. Цена ошибки — массовое неверное состояние, ручное восстановление, спор о границе операции и потеря времени команды. Если часть объектов успела измениться, старый input уже не описывает безопасный rollback.</p><p><strong>Тезис:</strong> безопасная автоматизация строится не вокруг одной кнопки, а вокруг цепочки связанных доказательств. Preview отвечает, что предлагается. Ограниченный scope отвечает, какие targets допустимы. Approval разрешает именно этот scope. Audit trail сохраняет переходы. Verification проверяет наблюдаемое состояние после исполнения. При разрыве связи процесс останавливается.</p><h2>Механизм: пять границ одной операции</h2><p>Массовая операция начинается с намерения, но не должна сразу получать право записи. Сначала система строит operation card: идентификатор, selector, точный список targets, digest списка, ожидаемый переход и лимит размера. Затем отдельные проверки связывают карточку с authority и approval.</p><p>У каждого барьера свой вопрос:</p><ul><li><strong>Preview.</strong> Что процесс предлагает изменить в данный момент?</li><li><strong>Bounded scope.</strong> Какие targets и какой максимум разрешены?</li><li><strong>Approval.</strong> Кто согласовал именно этот preview и этот scope?</li><li><strong>Audit trail.</strong> Какой переход состоялся и с какими идентификаторами?</li><li><strong>Verification.</strong> Что независимый источник наблюдает после операции?</li></ul><p>Эти ответы нельзя склеивать. Наличие preview не доказывает будущий результат. Approval не доказывает, что reviewer проверил смысл каждого изменения. Receipt исполнителя не доказывает состояние target system. Лог не делает операцию обратимой.</p><h2>Preview фиксирует намерение, а не обещает эффект</h2><p>Хороший preview показывает не фразу «обновить конфигурацию», а exact selection и proposed diff. Для списка targets нужны плотный массив идентификаторов, selector, exclusions и digest. Для изменения нужны expected before и expected after. Для расследования нужны operation id, версия правила и время построения.</p><p>Если selector вычисляется заново после approval, approval может относиться к другому набору. Поэтому исполнитель принимает только карточку с тем же <code>operationId</code>, <code>targetDigest</code>, selector и authority id. Любое несовпадение даёт stop. Список без digest можно прочитать, но его нельзя надёжно связать с последующим запросом.</p><p>У Terraform есть та же полезная граница: <code>terraform plan</code> создаёт execution plan и сам не выполняет предложенные изменения. Официальная документация отдельно предупреждает, что между speculative plan и применением состояние цели может измениться, поэтому перед применением нужен повторный non-speculative plan. Это пример того, почему preview нельзя считать вечным разрешением.</p><h2>Симптом → причина → проверка → действие</h2><div class=\"table-scroll\"><table><caption>Диагностика массовой операции</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>В preview есть только число targets</td><td>Операция не фиксирует exact selection и exclusions</td><td>Сравнить список ids, selector и target digest</td><td>Остановить approval до появления списка и лимита</td></tr><tr><td>Job получил approval, но selector вычисляется снова</td><td>Approval не связан с preview</td><td>Проверить operation id, selector и digest перед execute</td><td>Отклонить запрос при любом несовпадении</td></tr><tr><td>В журнале есть success, но состояние неизвестно</td><td>Receipt исполнителя приняли за observed state</td><td>Сделать независимое чтение authoritative source</td><td>Оставить статус stopped или verification-pending</td></tr><tr><td>Rollback запускает обратный payload автоматически</td><td>Старый preview используют как новое разрешение</td><td>Проверить observed state и новый scope</td><td>Создать отдельный rollback plan и новую authority</td></tr><tr><td>Операция затрагивает лишние targets</td><td>Лимит проверяют только в интерфейсе</td><td>Подать scope больше лимита и проверить отказ до approval</td><td>Перенести проверку max targets в executor</td></tr></tbody></table></div><figure><img src=\"/assets/editorial/2025/engineering-automation-2025-preview-approval-rollback.svg\" alt=\"Цепочка preview, ограниченного scope, authority, approval, execute, audit trail и verification с остановкой перед отдельным rollback plan\" loading=\"lazy\" /><figcaption>Каждая карточка отвечает на свой вопрос. Красная ветка останавливает операцию; rollback начинается только с нового плана и новой проверки.</figcaption></figure><h2>Учебная модель с fail-closed поведением</h2><p>Ниже — учебный пример в памяти. Он не читает targets, не отправляет запросы и не выполняет запись. Его задача — показать границу между preview, approval, receipt и verification. В реальной системе объекты должны получать identity, durable storage, policy evaluation и контроль доступа из конкретной инфраструктуры.</p><pre><code>const preview = { operationId: 'op-demo-17', selector: 'label=legacy', targetIds: ['doc-a', 'doc-b'], targetDigest: 'sha256:targets-a-b' }; const authority = { authorityId: 'role-maintainer', selector: 'label=legacy', maxTargets: 2 }; function requestApproval(p, a) { if (p.targetIds.length &gt; a.maxTargets) return { status: 'STOP', reason: 'scope-exceeds-authority-limit' }; if (p.selector !== a.selector) return { status: 'STOP', reason: 'selector-not-allowed' }; return { status: 'WAITING_APPROVAL', operationId: p.operationId }; } function execute(approved, p) { if (approved.operationId !== p.operationId || approved.targetDigest !== p.targetDigest) return { status: 'STOP', reason: 'approval-does-not-bind-preview' }; return { status: 'SIMULATED_RECEIPT', effect: 'учебная запись' }; }</code></pre><p>Первый отказ должен произойти до approval, если карточка содержит третий target. Второй — до simulated execute, если кто-то подменил digest после согласования. Отсутствующее поле, неизвестный operation id и лишнее поле также должны закрывать путь. Fail-closed означает, что неполный или подозрительный input не получает безопасный-looking default.</p><p><code>SIMULATED_RECEIPT</code> — только запись о прохождении учебной функции. Она не означает, что <code>doc-a</code> и <code>doc-b</code> изменились. Проверка результата потребовала бы отдельного чтения системы, которой в примере нет.</p><h2>Dry-run имеет собственную границу</h2><p>Preview и dry-run часто называют одним словом, хотя они отвечают за разные слои. Preview доменной операции может построить proposed diff из локальной модели. Dry-run конкретной команды может остановиться на клиенте или отправить проверяемый запрос серверу без сохранения.</p><p>Документация Kubernetes для <code>kubectl apply</code> разделяет режимы <code>--dry-run=client</code> и <code>--dry-run=server</code>. Client только печатает объект, который был бы отправлен. Server отправляет запрос без persistence ресурса. Значит, статус «dry-run прошёл» нужно сопровождать указанием режима, источника данных и границы. Ни один режим не гарантирует, что поздний реальный запрос встретит те же policy и состояние.</p><p>Если проверка использовала client dry-run, нельзя утверждать, что API-сервер примет запрос. Если server dry-run прошёл, нельзя утверждать, что через час selector вернёт тот же набор ресурсов. Следующее действие — повторить preconditions рядом с execute и сохранить новый digest.</p><h2>Approval связывает, но не оправдывает</h2><p>Поле <code>approved: true</code> слишком слабое. Минимальная связь включает operation id, preview digest, target digest, authority id, reviewer или service identity, policy version и срок действия. Если digest другой, согласие относится к другой операции. Если authority другой, неизвестно, какой лимит применялся. Если истёк срок, старое решение не должно продолжать жить.</p><p>GitHub Actions даёт официальный пример узкой контрольной точки: job, который ссылается на environment с required reviewers, ждёт approval до старта; доступ к environment secrets появляется только после прохождения protection rules. Это контроль допуска к запуску. Он не доказывает правильность diff, качество selector или факт изменения целевых объектов. Содержательный review остаётся отдельной проверкой.</p><h2>Audit trail и verification отвечают на разные вопросы</h2><p>Audit trail связывает переходы: operation id, preview digest, authority, approval, actor, время, receipt и stop reason. Он помогает восстановить последовательность без поиска по чату и stdout. Но свойства «append-only» и «невозможно подделать» нельзя получить одним названием поля. Их нужно обеспечивать конкретным хранилищем, правами записи, retention и проверкой целостности.</p><p>Verification начинается после execute и использует authoritative read. Сначала задайте expectation: например, на каждом target поле <code>label</code> должно иметь значение <code>current</code>, а число отсутствующих targets равно нулю. Затем укажите источник, допустимую задержку и правило частичного результата. Если source недоступен, status должен быть <code>verification-pending</code>, а не <code>verified</code>.</p><p>Зелёный exit code не заменяет observed state. Receipt сообщает, что executor дошёл до своей точки завершения. Verification сообщает, что внешний объект сейчас выглядит ожидаемым образом. Эти записи нельзя объединять в одно поле.</p><h2>Rollback — новая операция</h2><p>Автоматический inverse payload опасен. Target мог измениться вручную после запуска. Исходное значение могло быть неправильным. Часть объектов могла принять новое состояние, а часть — нет. Внешняя зависимость могла изменить порядок восстановления.</p><p>При остановке безопасно создать только rollback plan: сохранить причину, прочитать observed state, определить новый bounded scope, выбрать owner, построить новый preview и получить новую authority и approval. Статус <code>rollback-planned</code> не означает <code>rollback-executed</code>. Старое approval не даёт права на новый write.</p><h2>Порядок действий</h2><ol><li><strong>Выберите одну операцию.</strong> Начните с малого обратимого batch, а не с широкого selector на всей системе.</li><li><strong>Назовите exact targets.</strong> Запишите ids, selector, exclusions, digest, expected before/after и maximum scope.</li><li><strong>Разделите capability.</strong> Preview и approval не должны иметь права записи. Execute принимает только связанный approval.</li><li><strong>Проверьте отказ.</strong> Добавьте лишний target, подмените digest, удалите authority id и передайте неизвестную карточку. Для каждого входа ожидайте STOP.</li><li><strong>Привяжите approval.</strong> Сравните operation id, selector, target digest, authority id, policy version и срок действия непосредственно перед execute.</li><li><strong>Запишите receipt отдельно.</strong> Сохраните переход и stop reason. Не называйте receipt подтверждением состояния внешней системы.</li><li><strong>Выполните независимую verification.</strong> Прочитайте authoritative source, сравните expected и observed, зафиксируйте partial result и задержку.</li><li><strong>Остановите неясный результат.</strong> При недоступном source или несовпадении не делайте автоматический retry-write.</li><li><strong>Планируйте rollback заново.</strong> Используйте observed state и новый scope. Получите новое разрешение на отдельную операцию.</li></ol><h2>Ограничения</h2><p>Учебная модель не проверяет реальные permissions, identity provider, состояние базы, гонки между preview и execute, сетевые повторы, durable audit storage, scheduler, очереди или фактический rollback. Она также не доказывает, что selector выражает правильное бизнес-условие. Все значения кода фиксированы в памяти; production effect намеренно не выполняется.</p><p>Даже exact digest не решает проблему сам по себе. Хэш связывает представления, но не говорит, что selection полон, authority разумна, expected state корректен или reviewer понял риск. Нужны владельцы данных, политика доступа и источник истины. Чем дороже ошибка, тем меньше должна быть граница первой операции.</p><h2>Проверяемый критерий готовности</h2><p>Операция готова к ограниченному реальному запуску, если другой инженер может без устного объяснения восстановить exact targets, proposed diff, maximum scope, authority, approval binding, stop conditions, receipt и authoritative verification. Тест с подменой target digest останавливает executor до записи. Тест с лишним target останавливает approval до запуска. После execute существует отдельная проверка observed state. При несовпадении система не делает скрытый retry и создаёт только новый rollback plan.</p><p>Если хотя бы один пункт держится на внимательности оператора, автоматизацию не расширяют. Сначала переносят правило в contract и проверяют отрицательный путь. Практическая граница — не «job завершился успешно», а «система показывает, что именно было разрешено, что произошло и каким чтением проверен результат».</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://developer.hashicorp.com/terraform/cli/commands/plan\" target=\"_blank\" rel=\"noopener noreferrer\">HashiCorp Terraform: plan command</a> — официальное описание preview, speculative plan и необходимости перепроверить эффект перед apply.</li><li><a href=\"https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/control-deployments\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: controlling deployments</a> — официальное описание environments, protection rules и required reviewers.</li><li><a href=\"https://kubernetes.io/docs/reference/kubectl/generated/kubectl_apply/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: kubectl apply</a> — официальное описание client/server dry-run и их границ.</li></ul>"
}