Production readiness: ризик, дані та право на дію
Гарний patch не дає агенту права читати customer data, тримати production token чи застосовувати migration. Production readiness починається з межі authority, а не з фінального схвалення. На одному change сервісу CartRelay пройдемо шлях від невинного тікета до production rollout і подивимось, де саме закінчується свобода агента:
- класифікуємо ризик до першого prompt і повторно після discovery;
- запишемо capability envelope через конкретні дієслова і targets;
- відокремимо інструкцію від permission і від OS-level enforcement;
- не пустимо private data і persistent credentials у небезпечний context;
- передамо exact production intent захищеній automation;
- перевіримо rollout за runtime signal, а не за фактом approval.
CartRelay; справжні production credentials і customer records не використовуються.Ризик до першого prompt
Тікет Fix duplicate refund webhook звучить як звичайний bugfix: знайти причину, виправити handler, закрити. Але доки не відкриєте логи, ви не знаєте чотирьох речей:
- data - які дані зачепить розслідування;
- authority - куди агент зможе писати;
- blast radius - скільки refunds зачепить помилка;
- reversibility - що з цього взагалі оборотне.
Тому перше інженерне питання - не "як лагодити", а "що agent зможе зробити, якщо помилиться або витлумачить завдання ширше, ніж мав на увазі автор". Відповідь і є міткою ризику - і призначають її до prompt, бо сам prompt уже відкриває дані та tools.
Класифікація ризику обмежує не глибину міркування Claude, а його максимальну реальну владу. Думати модель може про будь-що - дотягнутися має тільки до дозволеного.
Швидка класифікація ризику
Швидка класифікація займає п'ять секунд і не потребує комітету: дивитесь, чого стосується завдання, і берете стартовий клас. Сумніваєтесь - мінімум review-required, і працюєте далі.
| Швидкий клас | Стартове припущення | Що негайно підвищує ризик |
|---|---|---|
| low-risk | docs, коментар, локальний rename | sensitive data, external publish, shared config |
| review-required | code, config, dependency, API | broad write, privileged credential, weak rollback |
| high-risk | production, DB, secrets, money | клас уже максимальний |
Тип файлу задає нижню межу, але не вирок. Будь-яка з чотирьох осей піднімає клас сама по собі:
- data - public fixture чи customer record;
- authority - read repo чи write production DB;
- blast radius - один test object чи всі активні refunds;
- reversibility - git revert чи вже надіслана виплата.
README з production token усередині - це high-risk, яким би невинним не виглядав diff. І клас не зафіксований назавжди: правило reclassify on discovery переперевіряє його щоразу, коли розслідування знаходить новий факт.
Capability envelope
Мітка review-required сама по собі нічого не забороняє. Працювати починає картка, де ризик перекладено в конкретні дієслова і targets, - capability envelope (рамка дозволених дій). "May help with refunds" не перевірити; edit refund handler - можна.
risk: review-required
may:
- read application code
- edit refund handler
- run local tests on synthetic fixtures
must_ask:
- fetch external incident links
must_not:
- read customer exports
- use production credentials
- write shared infrastructure
stop_when:
- fix requires data backfill or external refund action
До envelope входять не лише tools, а й environment з data class. А stop_when робить discovery безпечним: нова інформація не перетворюється автоматично на новий дозвіл - вона закриває стару картку.
Зверніть увагу, як межа змінює стратегію агента: замість "виконай cleanup" він готує план, fixture і verification evidence, бо пряма дія йому не дозволена.
CartRelay: зміна класу ризику
План був скромним: відтворити дубль, виправити idempotency check. Ось де він ламається:
1. Reproduce duplicate event on synthetic fixture OK
2. Patch idempotency check and add regression test OK
3. Inspect incident note: 312 affected refunds NEW FACT
4. Suggested cleanup touches production ledger STOP
5. Reclassify review-required -> high-risk REQUIRED
На третьому кроці початкова класифікація вже не відповідала завданню: в incident note знайшлися 312 зачеплених refunds, а запропонований cleanup пише в production ledger.
Схема показує розріз: новий факт закриває старий envelope, і далі живуть дві гілки з різною authority:
- code fix триває в старій зоні - його envelope не змінювався;
- production cleanup іде в окремий work item - зі своїм owner, exact target і окремим approval;
- перший envelope і його закриття лишаються в traceability.
Reclassification не відкочує корисну роботу - вона розділяє завдання на зони з різними authority boundaries. і жодного доступу "ще на хвилинку, тільки перевірити здогадку в production".
Три шари захисту
Перша реакція на зрослий ризик - дописати в CLAUDE.md сувору інструкцію "не чіпай production". Інструкція корисна, але це прохання, а не замок. Шари захисту відповідають на різні питання:
| Шар | Що робить | Чого не гарантує |
|---|---|---|
CLAUDE.md, rules, prompt | пояснює бажану поведінку | не блокує process чи network |
| permission rules, hooks, modes | вирішує, чи можна викликати tool | не завжди обмежує внутрішній effect процесу |
| sandbox, OS, network proxy, downstream IAM | обмежує досяжний effect | не розуміє business intent |
Середній шар теж не абсолютний. auto mode знижує втому від підтверджень, але classifier лишається ймовірнісним. Ручні prompts не кращі: коли підтверджуєте майже кожен запит, pop-up перетворюється на звичку. Для критичної інфраструктури судження моделі - додатковий signal, а не enforcement.
Автономію безпечніше підвищувати, зменшуючи capability, а не посилюючи формулювання prompt.
Командні deny rules
Особистої акуратності не вистачає на другу сесію та другого розробника. Командна політика живе в .claude/settings.json проєкту й починається з того, що заборонено всім - включно з subagents:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)",
"Edit(./infra/prod/**)",
"Bash(kubectl *)",
"Bash(terraform apply *)"
],
"ask": [
"Edit(./migrations/**)"
]
}
}
Порядок оцінки на схемі і є deny precedence: deny перевіряється раніше за ask і allow, тому ширший allow його не скасовує.
- чутливі шляхи застосунку закривають через
Read/Editdeny і sandbox settings, а вбудовані protected paths Claude Code - це окремий механізм; - Bash argument patterns - не заміна IAM: для production надійніші narrow wrapper і downstream authorization;
- критичні заборони фіксує managed policy - вони не мають залежати від звичок однієї людини.
Тест простий: policy має пережити нову сесію, іншого розробника та іншого subagent. Усе, що живе тільки в голові або в одному prompt, цей тест не проходить.
Середовища ізоляції
Окрема тека не забирає у процесу ваші ключі: ізоляція файлів та ізоляція authority - різні речі. Питати треба не "де я працюю", а "що процес усе ще може зробити назовні". Кожне середовище має на це свою чесну відповідь:
| Середовище | Добре обмежує | Не обмежує саме |
|---|---|---|
| branch | історію change | process, credentials, network |
| worktree | робочі файли паралельних завдань | home directory, inherited env, external APIs |
| Claude Bash sandbox | Bash filesystem і outbound domains | built-in tools без permission rules |
| container | dependencies і process tree | mounted sockets, injected secrets, unrestricted egress |
| VM чи cloud sandbox | host і session | provider data flow, overbroad identity, allowed outbound |
| staging | вплив на production traffic | shared state: чужі перевірки, спільні дані, ціна помилки не нульова |
Container із mounted Docker socket чи cloud admin token зберігає великий blast radius - назва технології сама по собі нічого не ізолює. Вибір середовища починається з failure mode, а не з імені інструмента.
Два сусідні поняття сюди не входять: dry-run показує план дій, але не ізолює помилку виконання, а staging перевіряє інтеграцію після локальної пісочниці, а не замість неї.
Claude Bash sandbox
Вбудований sandbox - це OS-межа навколо Bash і його child processes: filesystem і network. Read, Edit, WebFetch і MCP живуть поза цією межею - їх закривають permission rules. Config нижче ховає ~/.aws лише від sandboxed Bash: для built-in tools потрібне відповідне правило Read deny у permissions.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/.aws", "~/.ssh"]
},
"network": {
"allowedDomains": ["registry.npmjs.org"]
},
"credentials": {
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" }
]
}
}
}
- default read policy не приховує секрети - sensitive files і env vars перелічуються явно;
- network і filesystem працюють лише разом - відкритий egress перетворює read leak на data exfiltration;
- проєктний allowlist не жорстка межа: новий host іде в approval flow, а жорсткий lockdown доменів дає лише managed-шар;
allowUnsandboxedCommands: falseзакриває escape hatch, але для явнихexcludedCommandsmanaged-only lock немає - їхній список тримають порожнім або мінімальним;- на native Windows вбудований Bash sandbox недоступний - для робочої межі потрібні WSL2, container чи VM.
--dangerously-skip-permissions не створює sandbox.Витік із трьох частин
Щоб украсти private data, агенту не треба писати у файл і ламати permission. Досить, щоб в одній сесії зійшлися три речі, кожна з яких окремо виглядає невинно.
На схемі три входи одного exfiltration path: private data в контексті, untrusted content, який керує поведінкою агента, і відкритий outbound channel. У звичайній робочій сесії всі три непомітно поєднуються:
- private data - source code,
.env, customer trace, internal docs; - untrusted content - issue body, PR comment, fetched page, dependency output, MCP response;
- outbound channel - WebFetch,
curl, gist, webhook, image URL, запис у зовнішній service.
Захист - розірвати будь-яку ланку цього ланцюга:
- browsing task не отримує private data;
- sensitive task не читає untrusted content;
- task з обома входами позбавляється arbitrary egress.
Allowlist звужує поверхню, але довірений API теж приймає довільні дані - для чутливого workflow потрібен scoped operation чи credential proxy. Prompt injection стає production risk лише через доступний ланцюг capability.
Дані локальної сесії
CLI і tools виконуються на вашій машині, але дані розмови з моделлю передаються мережею: prompts, вибраний контекст і model outputs потрапляють до provider. Локальний subagent відокремлює контекст і tools, а не гарантує data residency.
На схемі два маршрути: верхнім маршрутом контекст потрапляє до provider, а звідти повертається output; нижній лишає локальні артефакти сесії. Перед чутливим розслідуванням варто зрозуміти обидва:
- Team, Enterprise, API, third-party providers, ZDR, MCP servers і cloud runners - у кожного свої retention contracts і data policies;
- перед запуском cloud-поверхні перевірте, які файли й метадані вивантажуються, - зазвичай це більше, ніж видимий diff;
- regulated workflow - HIPAA, PCI-DSS, on-prem-only - не вирішується міткою local: local subagent не дає data residency; для цього потрібні approved provider, contract і deployment-контур;
- до sensitive investigation зафіксуйте allowed provider, retention, region, third parties і cleanup локальних артефактів.
"Файл не покинув ноутбук" не означає, що його вміст не потрапив у model context - і водночас у локальний transcript.
Sanitization
Розслідуванню потрібні production-деталі, а контексту вони протипоказані. Вихід - fixture, з якого identity зникла, а причинний signal лишився. Raw export у Claude session не входить: у fixture його перетворює людина або deterministic redactor до сесії, Claude бачить лише результат. Порівняйте вхід і вихід:
{
"email": "customer@example.test",
"authorization": "Bearer live_token_redacted",
"event_id": "evt-real-8841",
"order_id": "order-real-771",
"amount_cents": 4999,
"retry_after_ms": 812
}
{
"customer_id": "CUST-17",
"authorization": "<removed>",
"event_id": "EVT-DUP-1",
"order_id": "ORDER-42",
"amount_cents": 4999,
"retry_after_ms": 812
}
Прибрали email, bearer token і реальні ідентифікатори. Лишилися duplicate event key, зв'язки між записами, amount class, порядок і timing - тому bug відтворюється:
- sanitized fixture отримує synthetic identifiers і зберігається окремо від raw incident export;
- regression test двічі подає
EVT-DUP-1і чекає один ledger effect - механіка такого тесту знайома з рівня 19, розгортати її наново не будемо; - другий розробник перевіряє обидві сторони: bug відтворюється, real identifiers відсутні.
Гарна sanitization - це transformation contract, а не ручний пошук очевидних email: контракт називає властивості даних, які зобов'язані вижити.
Межа credentials
Fix готовий, і настає момент, заради якого будувалася вся межа: хто тримає production credential? Відповідь коротка - не agent. Інакше будь-яка помилка, dependency script чи prompt injection успадкує production authority цілком.
Authority на схемі з'являється поетапно: Claude готує intent, незалежний reviewer схвалює, і лише після цього protected job отримує short-lived identity для exact target.
- local agent не бачить reusable production token навіть у masked prompt;
- OIDC чи secret broker прив'язує token до repo, workflow, ref, environment і короткого терміну життя;
- downstream IAM дозволяє одну operation на одному target;
- ініціатор change не підтверджує власний production job.
Approval керує моментом появи authority, а не лише моментом запуску script.
Реакція на витік секрету
Токен майнув у виводі команди - і рефлекс вимагає стерти рядок, почистити чат, переписати історію. Рефлекс помиляється: жодна з цих дій не відкликає credential. Порядок інший:
1. Stop session and affected job
2. Revoke or rotate credential
3. Inspect audit log and possible use
4. Replace dependent configuration safely
5. Remove secret from logs, transcripts and repository
6. Run secret scan and record follow-up
/clear, видалення чату і redaction не змінюють валідність token - його вже могли скопіювати;- немає права revoke - зупиніть job і покличте credential owner або incident channel; cleanup не продовжують, ніби containment уже стався;
- secret потрапив у Git - спочатку rotate, потім history cleanup: rewrite змінює commit IDs і координується окремо;
- logs зберігають audit value - cleanup не має знищити incident timeline;
- secret scanner підтверджує відсутність відомих копій, але не доводить, що credential не використали.
Витік секрету - це інцидент, навіть коли репозиторій private. Різниця лише в імовірності, а не в процедурі.
Deployment intent
Фінальний крок - не "Claude, задеплой". Claude пише deployment-intent.yaml - точний, перевірюваний документ про те, що і куди поїде. Право виконати change залишається за захищеним pipeline:
service: cartrelay-refunds
environment: production-eu1
artifact: sha256:7f4d-example
change: idempotency guard for duplicate webhook
database_write: false
forbidden:
- refund replay
- ledger cleanup
verification:
- duplicate_event_rate
- refund_mismatch_rate
rollback: previous artifact digest
| Учасник | Рішення |
|---|---|
| Claude | patch, tests, intent, expected signal, rollback draft |
| Reviewer | чи узгоджується exact change із business risk |
| Protected pipeline | exact artifact, target, policy, short-lived identity |
| Runtime control | canary, telemetry, abort, rollback |
Approval стосується digest і target, а не вільного тексту "deploy latest". Production DB cleanup лишається окремою high-risk операцією і не ховається всередині deploy. Людина не копіює команди в prod shell - вона видає дозвіл обмеженій automation.
Production boundary має виконуватися системою, яка не залежить від слухняності Claude.
Canary та умови зупинки
Reviewer перевіряє intent до side effect. Що відбувається після - питання вже не до reviewer, а до runtime:
canary_percent: 5
observe_minutes: 10
continue_if:
duplicate_event_rate: decreasing
refund_mismatch_rate: unchanged
abort_if:
error_rate_percent: "> 1"
ledger_invariant_failures: "> 0"
Життєвий цикл rollout на схемі: approval лише відкриває canary, а далі вирішує runtime signal - повний rollout чи abort з поверненням попереднього artifact.
- canary обмежує blast radius, але корисний лише з чутливим signal і автоматичним stop;
- rollback повертає попередній artifact, але не скасовує виконані грошові операції - тому
database_write: falseокремий invariant; - runtime anomaly закриває rollout незалежно від упевненого AI summary;
- повний team readiness gate, exception process і organization traceability - тема рівня 24.
Human approval і runtime automation закривають різні типи невизначеності: перший відповідає "чи варто пробувати", друга - "чи вийшло".
Картка права на дію
Change закритий. Уся його межа вміщується на одній картці: що агенту було можна, чого не можна, що доведено і що чесно лишилося недоведеним.
risk: high
agent_may:
- edit repository
- test sanitized fixture
- prepare deployment intent
agent_must_not:
- read customer export
- hold production credential
- write production database
evidence:
- regression test
- sanitized fixture review
- exact artifact digest
- independent deployment approval
- canary result
not_proven:
- absence of every hidden defect
- safety of a future refund backfill
owner: cartrelay-release-operator
Capability, data, environment і deployment target узгоджені з одним change. Секція not_proven - не слабкість, а чесність: canary не доводить відсутність усіх прихованих дефектів, а безпека майбутнього refund backfill - окреме рішення з окремою межею.
Наступний рівень перенесе цю картку з рівня одного change на рівень team policy, traceability і engineering culture.