# Ce a scris Claude Code din infrastructura noastră, și ce am refuzat să facem merge

Folosim Claude Code zilnic pentru muncă de infrastructură, pe o platformă pe care o operăm pentru un client din SUA: stack-uri CDK, scripturi de deploy, politici IAM, alarme, runbook-uri. Cam o treime din codul de infrastructură comis în ultimele șase luni a fost schițat mai întâi de el. Asta e o contabilitate sinceră: ce a scris și am făcut merge ca atare, ce a scris și am corectat, și cele patru propuneri pe care le-am refuzat, cu motivele, pentru că refuzurile sunt mai instructive decât reușitele.

## Forma muncii

Schimbările de infrastructură pe platforma asta trec prin aceeași revizuire de pull request ca și codul de aplicație, și output-ul agentului trece și el prin ea. Rulează într-un sandbox cu acces de citire la repository și un rol AWS limitat care poate face `cdk diff` pe staging și poate citi CloudWatch, dar nu poate face deploy. Fiecare schimbare pe care o propune e un branch și un PR. Un om citește diff-ul, citește output-ul `cdk diff` pe care agentul îl lipește în PR, și face merge sau nu. Deploy-urile sunt [același script dintotdeauna](/ro/blog/same-tag-deploy-and-the-deploy-script-without-ci), rulat de un om.

Configurația asta e motivul pentru care restul articolului e calm. Agentul poate propune orice; nu poate executa nimic. Fiecare dintre refuzurile de mai jos a fost prins la PR.

<div class="article-figure">
<svg viewBox="0 0 900 220" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pipeline pentru schimbările de infrastructură scrise de agent. Claude Code într-un sandbox cu acces de citire la repo și un rol AWS doar de citire pentru cdk diff și CloudWatch. Produce un branch și un pull request cu output-ul de diff lipit în el. Un om revizuiește și face merge sau refuză. Un om rulează scriptul de deploy. Agentul nu are permisiune de deploy la niciun pas.">
<defs><marker id="arrA" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4fffb0"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<rect x="20" y="40" width="200" height="120" rx="12" fill="#151b2e" stroke="#7b8cff" stroke-width="1.5"/><text x="120" y="64" text-anchor="middle" fill="#7b8cff" font-weight="700">Claude Code · sandbox</text><text x="120" y="90" text-anchor="middle" fill="#f1f3ff">repo: citire + branch</text><text x="120" y="108" text-anchor="middle" fill="#f1f3ff">AWS: cdk diff, citire CloudWatch</text><text x="120" y="136" text-anchor="middle" fill="#ff6b8a" font-size="11">fără deploy · fără scriere în AWS</text>
<line x1="222" y1="100" x2="258" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="260" y="40" width="200" height="120" rx="12" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="360" y="64" text-anchor="middle" fill="#ffd166" font-weight="700">pull request</text><text x="360" y="90" text-anchor="middle" fill="#f1f3ff">schimbarea de cod</text><text x="360" y="108" text-anchor="middle" fill="#f1f3ff">output-ul cdk diff lipit</text><text x="360" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">raționamentul în descriere</text>
<line x1="462" y1="100" x2="498" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="500" y="40" width="180" height="120" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="590" y="64" text-anchor="middle" fill="#4fffb0" font-weight="700">revizuire umană</text><text x="590" y="90" text-anchor="middle" fill="#f1f3ff">merge · corectare · refuz</text><text x="590" y="108" text-anchor="middle" fill="#f1f3ff">același standard ca la cod</text><text x="590" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">toate cele patru refuzuri prinse aici</text>
<line x1="682" y1="100" x2="718" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="720" y="40" width="160" height="120" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="800" y="64" text-anchor="middle" fill="#4fffb0" font-weight="700">deploy uman</text><text x="800" y="90" text-anchor="middle" fill="#f1f3ff">scriptul obișnuit</text><text x="800" y="108" text-anchor="middle" fill="#f1f3ff">staging, apoi prod</text><text x="800" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">agentul nu e implicat</text>
<text x="450" y="200" text-anchor="middle" fill="#9aa3c7">Agentul poate propune orice și executa nimic. Asta face restul articolului calm.</text>
</g>
</svg>
</div>

## Merge ca atare

**Alarme și dashboard-uri CloudWatch.** Cea mai bună categorie. Dat „alarmă pe DLQ-ul de email, rata de 5xx din App Runner și Aurora ACU la maxim timp de 10 minute”, a produs CDK corect cu praguri rezonabile, statisticile potrivite (p99, nu medie, pentru latență), `treatMissingData` setat cum trebuie și un topic SNS legat la canalul de on-call existent. Alarmele sunt plictisitoare, bine documentate și au un răspuns corect evident; ăsta e punctul dulce.

**Scriptul [exercițiului de restaurare](/ro/blog/backups-you-never-restored-are-not-backups).** Scriptul care reaplică PITR, TTL, stream-uri și tag-uri pe o tabelă DynamoDB restaurată, citind setările dorite din definițiile CDK. Vreo 120 de linii de TypeScript cu AWS SDK, corect din primul PR, inclusiv paginarea pe care oamenii o uită.

**Runbook-uri.** Dat extrasul de log al unui incident și soluția aplicată, scrie o pagină de runbook clară în formatul casei. Runbook-urile lui sunt mai bune decât ale noastre, pentru că nu sare peste pașii pe care îi consideră evidenți.

**Politica de trust a [rolului de deploy OIDC](/ro/blog/github-oidc-deploy-roles-per-aws-account).** Condiție `sub` corectă, audiență corectă, limitată la repository și branch. A adăugat și, necerut, un comentariu care explică de ce claim-ul `sub` trebuie potrivit cu `StringLike` pentru tiparul de branch, care e lucrul pe care oamenii îl greșesc.

**Propagarea tag-urilor de cost.** O schimbare transversală care adaugă tag-urile `CostCenter` și `Owner` la fiecare stack printr-un aspect. Mecanică, a atins 40 de fișiere, zero greșeli.

## Merge după corecturi

**Stack-ul de [VPC endpoint-uri](/ro/blog/nat-gateway-the-most-expensive-line-you-do-not-see).** A adăugat endpoint-urile corect, dar a pus endpoint-ul S3 ca endpoint de interfață, nu de gateway, ceea ce funcționează, dar costă 7 $ pe lună pentru ceva gratuit. Prins citind `cdk diff`. Genul de greșeală pe care ar face-o și un om care n-a mai făcut asta.

**O schimbare de concurență la un Lambda.** A setat concurență rezervată pe o funcție ca să protejeze un API din aval, ceea ce era cerința, dar a ales numărul citind rate limit-ul documentat al aval-ului și împărțind la durata medie a unei invocări. Aritmetica era corectă; presupunerea că invocările sunt distribuite uniform nu era. L-am înjumătățit. Raționamentul era în descrierea PR-ului, ceea ce a făcut corectura o conversație de un minut.

**Strângerea security group-urilor.** Cerut să reducă ingress-ul security group-ului Aurora doar la conectorul VPC al App Runner și la Lambda-uri, a făcut-o, și a scos și regula pentru bastion host-ul la care runbook-ul încă face referire pentru acces de urgență. Bastionul e oprit 99 % din timp, deci `cdk diff` în staging n-a arătat nimic stricat. Un om care știa de bastion a pus regula la loc. Agentul n-avea cum să știe, pentru că rolul bastionului era în capul cuiva, nu în repository. Acum e în repository.

## Refuzate

**1. „Adaugă `cdk deploy` la rolul agentului ca să pot verifica schimbările cap-coadă.”** Propus într-o descriere de PR ca îmbunătățire de eficiență, cu un argument bine construit: diff nu e același lucru cu deploy, unele erori apar doar la deploy, staging-ul e izolat. Toate adevărate. Refuzat, pentru că tot modelul se bazează pe faptul că agentul nu poate schimba starea AWS, iar „doar staging” e felul în care asta se erodează. Staging-ul împarte codebase-ul CDK cu producția; un bug de deploy în staging e o previzualizare a unuia în producție, și vrem ca un om să vadă previzualizarea.

**2. Un `RemovalPolicy.DESTROY` pe clusterul Aurora de staging „ca să facă demontarea mai rapidă”.** Corect izolat; staging-ul e de unică folosință. Refuzat pentru că același construct e instanțiat pentru producție cu un flag de mediu, și [am pierdut resurse din exact forma asta de schimbare](/ro/blog/cloudformation-deleted-our-app-runner-services). Regula în codebase-ul ăsta e că politicile de eliminare sunt `RETAIN` peste tot și demontarea e o operațiune manuală, numită. Agentul n-avea cum să știe istoria; CLAUDE.md o spune acum.

**3. O instrucțiune IAM cu wildcard ca „să deblocheze indexatorul de căutare”.** Indexatorul pica cu access denied pe un ARN nou de stream DynamoDB. Soluția propusă era `dynamodb:*` pe `*`, cu un comentariu „de strâns mai târziu”. Refuzat, evident, iar partea interesantă e că, întrebat pentru soluția minimă în schimb, a produs ARN-ul exact al stream-ului și cele patru acțiuni de care are nevoie consumatorul în mai puțin de un minut. Wildcard-ul nu era o limită de capabilitate; era calea minimei rezistențe, și revizuirea e ce a făcut cealaltă cale să fie luată.

**4. Mutarea secretelor din Secrets Manager în SSM pentru a economisi 8 $ pe lună.** O economie reală, calculată corect, și [un argument pe care l-am făcut și noi](/ro/blog/secrets-in-cdk-secrets-manager-ssm-or-nothing-in-the-template). Refuzat pentru cele două secrete în cauză, pentru că sunt rotite de Lambda-ul de rotație al Secrets Manager, pe care SSM nu-l are, iar PR-ul nu ținea cont de rotație. Jumătate a primit merge: cele trei secrete statice s-au mutat, cele două rotite au rămas.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bilanț pe șase luni. Merge ca atare: alarme și dashboard-uri, scriptul exercițiului de restaurare, runbook-uri, politica de trust OIDC, propagarea tag-urilor. Merge după corecturi: tipul de VPC endpoint, numărul de concurență Lambda, security group care a scos regula bastionului. Refuzate: permisiune de deploy pentru agent, politică de eliminare DESTROY pe un construct partajat, IAM wildcard pentru deblocare, mutarea secretelor rotite în SSM. Tipar: munca mecanică și bine documentată primește merge curat; orice depinde de istorie sau raza de impact are nevoie de om.">
<g font-family="Inter,system-ui,sans-serif" font-size="11">
<rect x="20" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="155" y="44" text-anchor="middle" fill="#4fffb0" font-weight="700">merge ca atare</text>
<g fill="#f1f3ff"><text x="40" y="72">alarme și dashboard-uri</text><text x="40" y="92">scriptul de setări pentru restaurare</text><text x="40" y="112">runbook-uri din loguri de incident</text><text x="40" y="132">politica de trust OIDC, cu comentariu</text><text x="40" y="152">tag-uri de cost în 40 de fișiere</text></g>
<text x="155" y="190" text-anchor="middle" fill="#9aa3c7">plictisitor · documentat · un răspuns corect</text><text x="155" y="208" text-anchor="middle" fill="#9aa3c7">punctul dulce</text>
<rect x="315" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="450" y="44" text-anchor="middle" fill="#ffd166" font-weight="700">merge după corecturi</text>
<g fill="#f1f3ff"><text x="335" y="72">endpoint S3: interfață, nu gateway</text><text x="335" y="92">concurență: calcul corect, model greșit</text><text x="335" y="112">security group: a scos bastionul</text></g>
<text x="450" y="150" text-anchor="middle" fill="#9aa3c7">fiecare corectură a fost o conversație de un minut</text><text x="450" y="168" text-anchor="middle" fill="#9aa3c7">pentru că raționamentul era în PR</text><text x="450" y="200" text-anchor="middle" fill="#9aa3c7">bastionul trăia într-un cap, nu în repo</text>
<rect x="610" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#ff6b8a" stroke-width="1.5"/><text x="745" y="44" text-anchor="middle" fill="#ff6b8a" font-weight="700">refuzate</text>
<g fill="#f1f3ff"><text x="630" y="72">permisiune de deploy pentru agent</text><text x="630" y="92">DESTROY pe un construct partajat</text><text x="630" y="112">dynamodb:* pe * „de strâns mai târziu”</text><text x="630" y="132">secrete rotite → SSM (merge pe jumătate)</text></g>
<text x="745" y="170" text-anchor="middle" fill="#9aa3c7">toate despre raza de impact sau istorie</text><text x="745" y="188" text-anchor="middle" fill="#9aa3c7">niciuna despre capabilitate</text><text x="745" y="208" text-anchor="middle" fill="#9aa3c7">toate prinse la PR</text>
<text x="450" y="242" text-anchor="middle" fill="#9aa3c7">Cam o treime din commit-urile de infra în șase luni au fost schițate de agent. Zero au fost deploy-ate de agent.</text>
</g>
</svg>
</div>

## Ce spune tiparul

Merge-urile sunt muncă mecanică, bine documentată, cu un singur răspuns corect. Corecturile sunt locuri în care răspunsul corect depindea de ceva care nu era în repository: bastionul, forma traficului. Refuzurile sunt toate despre raza de impact sau istorie, și niciunul nu e despre capabilitate: în fiecare caz agentul putea produce versiunea sigură în momentul în care i s-a cerut.

Deci munca nu e „poate agentul să scrie cod de infrastructură”. Poate. Munca e să faci repository-ul să conțină lucrurile care trăiau în capete: regula politicii de eliminare, scopul bastionului, de ce cele două secrete se rotesc. Fiecare refuz de mai sus a devenit o linie în CLAUDE.md sau un comentariu în construct, și aceeași propunere n-a mai revenit.

Iar granița sandbox-ului nu e negociabilă, oricât de bun ar fi argumentul de eficiență. Agentul schițează; omul face deploy. Ăsta e tot motivul pentru care o treime din infrastructură poate fi schițată de agent fără ca cineva să-și piardă somnul.

Dacă vrei să folosești un agent pe infrastructura ta și încerci să decizi unde e linia, [noi am tras-o o dată și te ajutăm s-o tragi pe a ta](/contact).
