{"id":178555,"date":"2026-11-05T10:23:30","date_gmt":"2026-11-05T09:23:30","guid":{"rendered":"https:\/\/fotc.com\/?p=178555"},"modified":"2026-11-05T10:23:38","modified_gmt":"2026-11-05T09:23:38","slug":"un-spend-cap-in-google-cloud-protejeaza-proiectul-un-detector-de-anomalii-ajuta-sa-intelegi-ce-se-intampla-pe-tot-contul","status":"publish","type":"post","link":"https:\/\/fotc.com\/ro\/blog\/un-spend-cap-in-google-cloud-protejeaza-proiectul-un-detector-de-anomalii-ajuta-sa-intelegi-ce-se-intampla-pe-tot-contul\/","title":{"rendered":"Un spend cap \u00een Google Cloud protejeaz\u0103 proiectul. Un detector de anomalii ajut\u0103 s\u0103 \u00een\u021belegi ce se \u00eent\u00e2mpl\u0103 pe tot contul"},"content":{"rendered":"\n<div class=\"wp-block-group fotc-article is-layout-constrained wp-block-group-is-layout-constrained\">\n<style data-wp-block-html=\"css\">\n\/* Tokens from FOTC-Design-System\/tokens (colors, typography, spacing) *\/\n.fotc-article{--fotc-red:#E3322B;--fotc-link:#C22B25;--fotc-link-hover:#9B221D;--fotc-amber:#FAB001;--fotc-green:#00A855;\n  --fotc-ink:#000000;--fotc-body:#4A4A4A;--fotc-muted:#6B6B6B;--fotc-ink300:#D1D1D1;\n  --fotc-surface:#F6F6F6;--fotc-border:#E8E8E8;--fotc-border-subtle:#F0F0F0;\n  --fotc-accent-soft:#EBF7FC;--fotc-accent-100:#D1ECF8;--fotc-radius:12px;\n  --fotc-shadow:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);--fotc-shadow-md:0 6px 16px rgba(11,27,43,.08);\n  --fotc-ease:cubic-bezier(.2,0,0,1);\n  font-family:'Fira Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif;\n  font-size:1rem;font-weight:300;color:var(--fotc-body);line-height:1.55;max-width:46rem;\n  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}\n.fotc-article h1,.fotc-article h2,.fotc-article h3{font-weight:500;line-height:1.15;letter-spacing:-.01em;color:var(--fotc-ink);text-wrap:balance}\n.fotc-article h1{font-size:2.25rem;margin:0 0 1.25rem}\n.fotc-article h2{font-size:1.75rem;margin:2.75rem 0 .75rem}\n.fotc-article h3{font-size:1.25rem;margin:1.75rem 0 .5rem}\n.fotc-article p{margin:0 0 1rem;text-wrap:pretty}\n.fotc-article .fotc-lead{font-size:1.125rem;color:#333333}\n.fotc-article a{color:var(--fotc-link);text-decoration:none;transition:color .12s}\n.fotc-article a:hover{color:var(--fotc-link-hover)}\n.fotc-article :focus-visible{outline:3px solid #F5B5B3;outline-offset:2px}\n.fotc-article strong{font-weight:500;color:var(--fotc-ink)}\n.fotc-article ul{margin:0 0 1rem 1.25rem;padding:0}\n.fotc-article li{margin:0 0 .4rem}\n.fotc-article .fotc-eyebrow{display:inline-block;font-size:.72rem;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--fotc-accent);margin-bottom:.5rem}\n\/* reveal (only when JS is present) *\/\n.fotc-article.fotc-js [data-reveal]{opacity:0;transform:translateY(14px);transition:opacity .6s var(--fotc-ease),transform .6s var(--fotc-ease)}\n.fotc-article.fotc-js [data-reveal].is-in{opacity:1;transform:none}\n\/* stats *\/\n.fotc-article .fotc-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:1.75rem 0}\n.fotc-article .fotc-stat{position:relative;overflow:hidden;border:1px solid var(--fotc-border);border-radius:var(--fotc-radius);padding:14px 16px;box-shadow:var(--fotc-shadow);background:#fff;transition:transform .25s var(--fotc-ease),box-shadow .25s var(--fotc-ease)}\n.fotc-article .fotc-stat:hover{transform:translateY(-3px);box-shadow:var(--fotc-shadow-md)}\n.fotc-article .fotc-stat::after{content:\"\";position:absolute;left:0;right:0;bottom:0;height:3px;background:var(--fotc-accent);transform:scaleX(0);transform-origin:left;transition:transform .8s var(--fotc-ease)}\n.fotc-article .fotc-stat.is-in::after{transform:scaleX(1)}\n.fotc-article .fotc-stat b{display:block;font-size:1.75rem;font-weight:500;color:var(--fotc-accent);line-height:1.1;font-variant-numeric:tabular-nums}\n.fotc-article .fotc-stat span{display:block;font-size:.8125rem;color:var(--fotc-muted);margin-top:6px;line-height:1.4}\n\/* figures *\/\n.fotc-article figure{margin:1.75rem 0;padding:16px 16px 12px;border:1px solid var(--fotc-border);border-radius:var(--fotc-radius);background:#fff;box-shadow:var(--fotc-shadow)}\n.fotc-article .fotc-chart{width:100%;height:auto;display:block}\n.fotc-article figcaption{font-size:.875rem;color:var(--fotc-muted);margin-top:.75rem;line-height:1.45}\n.fotc-article .fotc-fig-head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px}\n.fotc-article .fotc-fig-title{font-size:.9375rem;font-weight:500;color:var(--fotc-ink)}\n.fotc-article .fotc-controls{display:flex;gap:8px;flex-wrap:wrap;align-items:center}\n.fotc-article .fotc-btn{appearance:none;border:1px solid var(--fotc-border);background:#fff;color:var(--fotc-ink);font:inherit;font-size:.8125rem;font-weight:500;padding:6px 12px;border-radius:999px;cursor:pointer;transition:all .15s var(--fotc-ease)}\n.fotc-article .fotc-btn:hover{border-color:var(--fotc-accent);color:var(--fotc-accent)}\n.fotc-article .fotc-btn.is-on{background:var(--fotc-accent);border-color:var(--fotc-accent);color:#fff}\n.fotc-article .fotc-btn--dot::before{content:\"\";display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--dot);margin-right:7px;vertical-align:0}\n.fotc-article .fotc-range{display:flex;align-items:center;gap:8px;font-size:.8125rem;color:var(--fotc-muted)}\n.fotc-article .fotc-range input{accent-color:var(--fotc-accent);width:120px}\n.fotc-article .fotc-range output{font-variant-numeric:tabular-nums;color:var(--fotc-ink);font-weight:500;min-width:3.5ch}\n.fotc-article .fotc-tip{position:fixed;z-index:50;pointer-events:none;background:var(--fotc-ink);color:#fff;font-size:12.5px;padding:6px 10px;border-radius:8px;opacity:0;transition:opacity .1s;white-space:nowrap;box-shadow:var(--fotc-shadow-md)}\n\/* timeline *\/\n.fotc-article .fotc-timeline .tl-fill{stroke-dasharray:100;stroke-dashoffset:100}\n.fotc-article .fotc-timeline.is-play .tl-fill{animation:fotc-draw 2.6s var(--fotc-ease) forwards}\n.fotc-article .fotc-timeline.is-play .tl-cursor{animation:fotc-cursor 2.6s var(--fotc-ease) forwards}\n.fotc-article .fotc-timeline .tl-mark .tl-dot{transition:transform .3s var(--fotc-ease);transform-box:fill-box;transform-origin:center}\n.fotc-article .fotc-timeline .tl-mark.is-lit .tl-dot{transform:scale(1.25)}\n.fotc-article .fotc-timeline .tl-mark.is-lit .tl-pulse{animation:fotc-pulse 1.2s ease-out 1}\n.fotc-article .fotc-timeline .tl-stop,.fotc-article .fotc-timeline .tl-stoplabel{transition:opacity .4s}\n.fotc-article .fotc-timeline.is-done .tl-stop,.fotc-article .fotc-timeline.is-done .tl-stoplabel{opacity:1}\n.fotc-article .fotc-timeline.is-done .tl-stop{animation:fotc-march 1.2s linear infinite}\n@keyframes fotc-march{to{stroke-dashoffset:-12}}\n.fotc-article.no-js .fotc-timeline .tl-fill,.fotc-article .fotc-timeline.is-static .tl-fill{stroke-dashoffset:0}\n@keyframes fotc-draw{to{stroke-dashoffset:0}}\n@keyframes fotc-cursor{to{transform:translateX(var(--tl-x))}}\n@keyframes fotc-pulse{0%{opacity:.9;r:9}100%{opacity:0;r:26}}\n\/* attack *\/\n.fotc-article .fotc-attack rect{fill:#fff;stroke:var(--fotc-border);stroke-width:1.5;transition:all .35s var(--fotc-ease)}\n.fotc-article .fotc-attack .t{fill:var(--fotc-ink)} .fotc-article .fotc-attack .s{fill:var(--fotc-muted)}\n.fotc-article .fotc-attack .at-step,.fotc-article .fotc-attack .at-link,.fotc-article .fotc-attack .at-layer,.fotc-article .fotc-attack .at-verdict{opacity:.35;transition:opacity .35s var(--fotc-ease)}\n.fotc-article .fotc-attack .at-path{opacity:.35;stroke-dasharray:100;stroke-dashoffset:100;transition:opacity .3s}\n.fotc-article .fotc-attack .at-path.at-miss{stroke:var(--fotc-ink300)}\n.fotc-article .fotc-attack .at-path.at-hit{stroke:var(--fotc-accent)}\n.fotc-article .fotc-attack .at-verdict.at-miss{fill:var(--fotc-red)} .fotc-article .fotc-attack .at-verdict.at-hit{fill:var(--fotc-accent)}\n.fotc-article .fotc-attack .is-lit{opacity:1}\n.fotc-article .fotc-attack .at-path.is-lit{animation:fotc-draw .7s var(--fotc-ease) forwards}\n.fotc-article .fotc-attack .at-step.is-lit rect{stroke:var(--fotc-red)}\n.fotc-article .fotc-attack .at-layer.at-miss.is-lit rect{fill:var(--fotc-surface);stroke:var(--fotc-ink300)}\n.fotc-article .fotc-attack .at-layer.at-hit.is-lit rect{fill:var(--fotc-accent-soft);stroke:var(--fotc-accent);stroke-width:2.5}\n.fotc-article .fotc-attack .at-runner{transition:cx .5s var(--fotc-ease),cy .5s var(--fotc-ease),opacity .3s}\n.fotc-article .fotc-attack.is-static .at-step,.fotc-article .fotc-attack.is-static .at-link,.fotc-article .fotc-attack.is-static .at-layer,.fotc-article .fotc-attack.is-static .at-verdict{opacity:1}\n.fotc-article .fotc-attack.is-static .at-path{opacity:1;stroke-dashoffset:0}\n\/* coverage grid *\/\n.fotc-article .fotc-grid{display:grid;grid-template-columns:repeat(30,1fr);gap:4px;margin:6px 0 10px}\n.fotc-article .fotc-cell{aspect-ratio:1;border-radius:3px;background:var(--fotc-ink300);transition:transform .2s var(--fotc-ease),opacity .25s,background .35s;cursor:default}\n.fotc-article .fotc-cell[data-s=\"api\"]{background:var(--fotc-green)}\n.fotc-article .fotc-cell[data-s=\"man\"]{background:var(--fotc-amber)}\n.fotc-article .fotc-cell:hover{transform:scale(1.35);position:relative;z-index:2}\n.fotc-article .fotc-grid.f-api .fotc-cell:not([data-s=\"api\"]),.fotc-article .fotc-grid.f-man .fotc-cell:not([data-s=\"man\"]),.fotc-article .fotc-grid.f-none .fotc-cell:not([data-s=\"none\"]){opacity:.15}\n.fotc-article .fotc-legend{display:flex;gap:14px;flex-wrap:wrap;font-size:.8125rem;color:var(--fotc-muted)}\n.fotc-article .fotc-legend b{font-weight:500;color:var(--fotc-ink);font-variant-numeric:tabular-nums}\n.fotc-article .fotc-zout{font-size:.875rem;color:var(--fotc-muted);margin:.5rem 0 0} .fotc-article .fotc-zout b{font-weight:500}\n.fotc-article .fotc-bars{display:flex;height:10px;border-radius:999px;overflow:hidden;background:var(--fotc-ink300);margin:8px 0 6px}\n.fotc-article .fotc-bars i{display:block;height:100%;transition:width .5s var(--fotc-ease)}\n\/* cadence *\/\n.fotc-article .fotc-bar rect{transition:height .7s var(--fotc-ease),y .7s var(--fotc-ease),opacity .25s,fill .25s}\n.fotc-article .fotc-bar:hover rect{opacity:.75}\n.fotc-article .fotc-cadence.only-alerts .fotc-bar.no-alert{opacity:.18}\n.fotc-article .fotc-bar circle,.fotc-article .fotc-bar text{transition:opacity .4s}\n\/* cards *\/\n.fotc-article .fotc-cols{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:1.25rem 0 1.75rem}\n.fotc-article .fotc-col{border-radius:var(--fotc-radius);padding:14px 16px;border:1px solid var(--fotc-border)}\n.fotc-article .fotc-col--stop{border-top:4px solid var(--fotc-red)}\n.fotc-article .fotc-col--flow{border-top:4px solid var(--fotc-accent)}\n.fotc-article .fotc-col h3{font-size:1rem;margin:0 0 .5rem}\n.fotc-article .fotc-col ul{margin:0 0 0 1.1rem;font-size:.9375rem}\n.fotc-article .fotc-cards{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:1.5rem 0}\n.fotc-article .fotc-card{border:1px solid var(--fotc-border);border-radius:var(--fotc-radius);padding:14px 16px;background:#fff;box-shadow:var(--fotc-shadow);transition:transform .25s var(--fotc-ease),box-shadow .25s var(--fotc-ease),border-color .25s}\n.fotc-article .fotc-card:hover{transform:translateY(-3px);box-shadow:var(--fotc-shadow-md);border-color:var(--fotc-accent-100)}\n.fotc-article .fotc-card h3{font-size:1rem;margin:0 0 .4rem}\n.fotc-article .fotc-card p{font-size:.9375rem;margin:0;color:var(--fotc-body)}\n.fotc-article .fotc-card .fotc-tag{display:inline-block;font-size:.7rem;font-weight:500;letter-spacing:.04em;text-transform:uppercase;color:var(--fotc-red);margin-bottom:.4rem}\n.fotc-article.fotc-js .fotc-cards [data-reveal]:nth-child(2){transition-delay:.1s} .fotc-article.fotc-js .fotc-cards [data-reveal]:nth-child(3){transition-delay:.2s} .fotc-article.fotc-js .fotc-cards [data-reveal]:nth-child(4){transition-delay:.3s}\n.fotc-article .fotc-callout{margin:1.75rem 0;padding:1rem 1.25rem;border-left:4px solid var(--fotc-accent);background:var(--fotc-accent-soft);border-radius:0 var(--fotc-radius) var(--fotc-radius) 0}\n.fotc-article .fotc-callout p:last-child{margin-bottom:0}\n\/* terminal *\/\n.fotc-article .fotc-term{background:#1B1D21;border-radius:var(--fotc-radius);overflow:hidden;box-shadow:var(--fotc-shadow-md)}\n.fotc-article .fotc-term-bar{display:flex;align-items:center;gap:6px;padding:10px 14px;background:#25282E;color:#9AA0A6;font-size:.75rem}\n.fotc-article .fotc-term-bar i{width:10px;height:10px;border-radius:50%;background:#3C4043} .fotc-article .fotc-term-bar span{margin-left:8px}\n.fotc-article .fotc-term-body{margin:0;padding:14px 16px 16px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78rem;line-height:1.6;color:#E8EAED;overflow-x:auto;min-height:23em}\n.fotc-article .fotc-term-body .tl{display:block;white-space:pre-wrap;min-height:1.6em}\n.fotc-article .fotc-term-body .cmd{color:#8AB4F8}\n.fotc-article .fotc-term-body .hl-red{color:#FF8A80} .fotc-article .fotc-term-body .hl-amber{color:#FDD663}\n.fotc-article .fotc-term-body b{font-weight:500;color:inherit;opacity:.9}\n.fotc-article .fotc-term-body .caret::after{content:\"\u258d\";color:#E8EAED;animation:fotc-blink 1s steps(1) infinite}\n.fotc-article.fotc-js .fotc-term.is-anim .tl{opacity:0} .fotc-article.fotc-js .fotc-term.is-anim .tl.is-on{opacity:1;transition:opacity .15s}\n.fotc-article.fotc-js .fotc-term.is-anim .tl.hl-red.is-on,.fotc-article.fotc-js .fotc-term.is-anim .tl.hl-amber.is-on{animation:fotc-flash .9s ease-out 1}\n@keyframes fotc-blink{50%{opacity:0}}\n@keyframes fotc-flash{0%{background:rgba(255,255,255,.18)}100%{background:transparent}}\n.fotc-article .fotc-tag--blue{color:var(--fotc-accent)}\n\/* code *\/\n.fotc-article .fotc-code{position:relative;margin:1.25rem 0}\n.fotc-article .fotc-code pre{margin:0;padding:1rem 1.25rem;overflow-x:auto;background:var(--fotc-surface);border:1px solid var(--fotc-border-subtle);border-radius:var(--fotc-radius);font-size:.85rem;line-height:1.5;color:#333333;max-height:280px;transition:max-height .4s var(--fotc-ease)}\n.fotc-article .fotc-code.is-open pre{max-height:2000px}\n.fotc-article .fotc-code .fotc-code-bar{display:flex;justify-content:flex-end;gap:6px;margin-bottom:8px}\n.fotc-article .fotc-code .fotc-fade{position:absolute;left:0;right:0;bottom:0;height:60px;background:linear-gradient(transparent,var(--fotc-surface));border-radius:0 0 var(--fotc-radius) var(--fotc-radius);pointer-events:none}\n.fotc-article .fotc-code.is-open .fotc-fade{display:none}\n.fotc-article code{font-family:SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace}\n.fotc-article p code{font-size:.9em;background:var(--fotc-surface);border:1px solid var(--fotc-border-subtle);padding:.05em .35em;border-radius:4px;color:#333333}\n.fotc-article .fotc-faq h3{font-size:1.0625rem;margin-top:1.5rem}\n.fotc-article .fotc-note{font-size:.875rem;color:var(--fotc-muted);border-top:1px solid var(--fotc-border);margin-top:2.5rem;padding-top:1rem}\n@media (prefers-reduced-motion:reduce){.fotc-article *{transition:none!important;animation-duration:.01s!important}.fotc-article.fotc-js [data-reveal]{opacity:1;transform:none}}\n@media (max-width:640px){.fotc-article h1{font-size:1.75rem}.fotc-article h2{font-size:1.375rem}\n  .fotc-article .fotc-stats{grid-template-columns:1fr 1fr}.fotc-article .fotc-cols,.fotc-article .fotc-cards{grid-template-columns:1fr}.fotc-article .fotc-grid{grid-template-columns:repeat(20,1fr)}}\n\/* tables *\/\n.fotc-article .fotc-table-scroll{overflow-x:auto;margin:1.25rem 0}\n.fotc-article table{width:100%;border-collapse:collapse;margin:0;font-size:.9rem;font-variant-numeric:tabular-nums}\n.fotc-article th{text-align:left;font-weight:500;color:var(--fotc-ink);border-bottom:2px solid var(--fotc-accent);padding:.55rem .5rem;vertical-align:bottom}\n.fotc-article td{border-bottom:1px solid var(--fotc-border);padding:.55rem .5rem;vertical-align:top}\n.fotc-article td:first-child{font-weight:500;color:var(--fotc-ink)}\n.fotc-article tr:nth-child(even) td{background:var(--fotc-surface)}\n<\/style>\n\n<script data-wp-block-html=\"js\">\n(function () {\nfunction init() {\n  var root = document.querySelector(\".fotc-article\"); if (!root) return;\n  var LX = {\"day\": \"ziua\", \"per_run\": \"conturi per rulare\", \"alert1\": \"alert\u0103\", \"alertN\": \"alerte\", \"legend\": \"alerte trimise oamenilor\", \"obs\": \"observa\u021bii\", \"project\": \"proiect\", \"service\": \"serviciu\", \"api\": \"cap confirmat prin API\", \"man\": \"cap declarat de un om\", \"none\": \"f\u0103r\u0103 limit\u0103\", \"mean\": \"medie\", \"median\": \"median\u0103\", \"incident\": \"incident\", \"day1\": \"ziua 1\", \"day21\": \"ziua 21 \u00b7 ax\u0103 r\u0103d\u0103cin\u0103 p\u0103trat\u0103\", \"days\": \" zile\", \"copy\": \"Copiaz\u0103\", \"copied\": \"Copiat\", \"expand\": \"Extinde\", \"collapse\": \"Restr\u00e2nge\", \"zout\": \"A doua zi a incidentului: z cu medie \u0219i abatere standard {a}, z cu median\u0103 \u0219i MAD {b}.\"};\n  root.classList.add(\"fotc-js\");\n  var reduce = window.matchMedia ? matchMedia(\"(prefers-reduced-motion: reduce)\").matches : false;\n  var NS = \"http:\/\/www.w3.org\/2000\/svg\";\n  var el = function (t, a, p) { var e = document.createElementNS(NS, t); for (var k in a) e.setAttribute(k, a[k]); if (p) p.appendChild(e); return e; };\n  var tip = document.getElementById(\"fotcTip\");\n  var showTip = function (e, text) { tip.textContent = text; tip.style.left = (e.clientX + 14) + \"px\"; tip.style.top = (e.clientY - 10) + \"px\"; tip.style.opacity = 1; };\n  var hideTip = function () { tip.style.opacity = 0; };\n  var lt = function (a, b) { return Math.sign(b - a) === 1; };\n  var fmt = function (n) { return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, \"\u00a0\"); };\n\n  \/* ---- reveal + one-shot triggers ---- *\/\n  var onIn = {};\n  var io = (\"IntersectionObserver\" in window) ? new IntersectionObserver(function (es) {\n    es.forEach(function (en) {\n      if (!en.isIntersecting) return;\n      en.target.classList.add(\"is-in\");\n      var k = en.target.getAttribute(\"data-trigger\"); if (onIn[k]) { onIn[k](); delete onIn[k]; }\n      io.unobserve(en.target);\n    });\n  }, { threshold: 0.3 }) : null;\n  root.querySelectorAll(\"[data-reveal],[data-trigger]\").forEach(function (n) { if (io) io.observe(n); else { n.classList.add(\"is-in\"); var k = n.getAttribute(\"data-trigger\"); if (onIn[k]) onIn[k](); } });\n\n  \/* ---- stats count-up ---- *\/\n  root.querySelectorAll(\".fotc-stat b[data-count]\").forEach(function (b) {\n    var target = +b.getAttribute(\"data-count\"), prefix = b.getAttribute(\"data-prefix\") || \"\";\n    b.textContent = prefix + \"0\";\n    onIn[\"stat-\" + target] = function () {\n      if (reduce) { b.textContent = prefix + target; return; }\n      var t0 = performance.now(), dur = 1100;\n      (function tick(t) { var p = Math.min(1, (t - t0) \/ dur), e = 1 - Math.pow(1 - p, 3); b.textContent = prefix + Math.round(target * e); if (p !== 1) requestAnimationFrame(tick); })(t0);\n    };\n    b.closest(\".fotc-stat\").setAttribute(\"data-trigger\", \"stat-\" + target);\n    if (io) io.observe(b.closest(\".fotc-stat\"));\n  });\n\n  \/* ---- spend cap timeline ---- *\/\n  var tl = root.querySelector(\".fotc-timeline\");\n  if (tl) {\n    var fill = tl.querySelector(\".tl-fill\"), marks = tl.querySelectorAll(\".tl-mark\"), timers = [];\n    tl.style.setProperty(\"--tl-x\", (fill.getAttribute(\"x2\") - fill.getAttribute(\"x1\")) + \"px\");\n    var play = function () {\n      timers.forEach(clearTimeout); timers = [];\n      tl.classList.remove(\"is-play\", \"is-done\", \"is-static\"); marks.forEach(function (m) { m.classList.remove(\"is-lit\"); });\n      if (reduce) { tl.classList.add(\"is-static\", \"is-done\"); marks.forEach(function (m) { m.classList.add(\"is-lit\"); }); return; }\n      void tl.getBoundingClientRect(); tl.classList.add(\"is-play\");\n      marks.forEach(function (m) { timers.push(setTimeout(function () { m.classList.add(\"is-lit\"); }, 2600 * (+m.getAttribute(\"data-at\") \/ 0.72))); });\n      timers.push(setTimeout(function () { tl.classList.add(\"is-done\"); }, 2700));\n      timers.push(setTimeout(play, 7000));\n    };\n    onIn[\"timeline\"] = play;\n    if (\"IntersectionObserver\" in window) new IntersectionObserver(function (es) { es.forEach(function (en) { if (!en.isIntersecting) { timers.forEach(clearTimeout); timers = []; } else if (!timers.length) { if (tl.classList.contains(\"is-play\")) play(); } }); }, { threshold: 0.2 }).observe(tl);\n  }\n\n  \/* ---- attack path ---- *\/\n  var at = root.querySelector(\".fotc-attack\");\n  if (at) {\n    var atTimers = [];\n    var lit = function (sel) { at.querySelectorAll(sel).forEach(function (n) { n.classList.add(\"is-lit\"); }); };\n    var runner = at.querySelector(\".at-runner\");\n    var playAt = function () {\n      atTimers.forEach(clearTimeout); atTimers = [];\n      at.classList.remove(\"is-static\"); at.querySelectorAll(\".is-lit\").forEach(function (n) { n.classList.remove(\"is-lit\"); });\n      if (reduce) { at.classList.add(\"is-static\"); return; }\n      var t = 0, step = 520;\n      runner.setAttribute(\"opacity\", 1); runner.setAttribute(\"cx\", 145); runner.setAttribute(\"cy\", 20);\n      for (var i = 0; i !== 4; i++) (function (i) {\n        atTimers.push(setTimeout(function () { lit(\".at-step-\" + i); runner.setAttribute(\"cy\", 20 + i * 72 + 28); if (i !== 3) lit(\".at-link-\" + i); }, t)); t += step;\n      })(i);\n      [0, 1].forEach(function (i) { atTimers.push(setTimeout(function () { lit(\".at-path-\" + i); lit(\".at-layer-\" + i); lit(\".at-verdict-\" + i); }, t)); t += step; });\n      atTimers.push(setTimeout(function () { lit(\".at-path-2\"); }, t)); t += 500;\n      atTimers.push(setTimeout(function () { lit(\".at-layer-2\"); lit(\".at-verdict-2\"); runner.setAttribute(\"cx\", 565); runner.setAttribute(\"cy\", 48 + 2 * 90 + 30); runner.setAttribute(\"fill\", \"#0097DA\"); }, t)); t += 6500;\n      atTimers.push(setTimeout(function () { runner.setAttribute(\"opacity\", 0); runner.setAttribute(\"fill\", \"#E3322B\"); }, t - 600));\n      atTimers.push(setTimeout(playAt, t));\n    };\n    onIn[\"attack\"] = playAt;\n    if (\"IntersectionObserver\" in window) new IntersectionObserver(function (es) { es.forEach(function (en) { if (!en.isIntersecting) { atTimers.forEach(clearTimeout); atTimers = []; } else if (!atTimers.length) { if (at.querySelector(\".is-lit\")) playAt(); } }); }, { threshold: 0.2 }).observe(at);\n  }\n\n  \/* ---- coverage grid ---- *\/\n  var grid = root.querySelector(\".fotc-grid\");\n  if (grid) {\n    var cols = 30, rows = 8, seed = 3;\n    var rnd = function () { seed = (seed * 9301 + 49297) % 233280; return seed \/ 233280; };\n    var cells = [];\n    for (var r = 0; r !== rows; r++) for (var c = 0; c !== cols; c++) {\n      var p = rnd(), w = 1 - c \/ cols, s = lt(p, 0.18 + 0.3 * w) ? \"api\" : lt(p, 0.4 + 0.35 * w) ? \"man\" : \"none\";\n      var d = document.createElement(\"div\"); d.className = \"fotc-cell\"; d.setAttribute(\"data-s\", s); d.setAttribute(\"data-c\", c);\n      d.setAttribute(\"data-label\", LX.project + \" \" + (c + 1) + \" \u00b7 \" + LX.service + \" \" + (r + 1)); grid.appendChild(d); cells.push(d);\n    }\n    var names = { api: LX.api, man: LX.man, none: LX.none };\n    var bars = root.querySelector(\".fotc-bars\"), legend = root.querySelector(\".fotc-legend\");\n    var recount = function () {\n      var n = { api: 0, man: 0, none: 0 }; cells.forEach(function (d) { n[d.getAttribute(\"data-s\")]++; });\n      var tot = cells.length;\n      if (bars) { bars.textContent = \"\"; [[n.api, \"#00A855\"], [n.man, \"#FAB001\"]].forEach(function (s) { var i = document.createElement(\"i\"); i.style.width = (100 * s[0] \/ tot) + \"%\"; i.style.background = s[1]; bars.appendChild(i); }); }\n      if (legend) legend.querySelectorAll(\"[data-n]\").forEach(function (x) { x.textContent = n[x.getAttribute(\"data-n\")]; });\n    };\n    recount();\n    grid.addEventListener(\"mousemove\", function (e) { var d = e.target.closest(\".fotc-cell\"); if (d) showTip(e, d.getAttribute(\"data-label\") + \" \u00b7 \" + names[d.getAttribute(\"data-s\")]); });\n    grid.addEventListener(\"mouseleave\", hideTip);\n    root.querySelectorAll(\"[data-filter]\").forEach(function (b) {\n      b.addEventListener(\"click\", function () {\n        var f = b.getAttribute(\"data-filter\"), on = b.classList.contains(\"is-on\");\n        root.querySelectorAll(\"[data-filter]\").forEach(function (x) { x.classList.remove(\"is-on\"); });\n        grid.classList.remove(\"f-api\", \"f-man\", \"f-none\");\n        if (!on) { if (f !== \"all\") { b.classList.add(\"is-on\"); grid.classList.add(\"f-\" + f); } }\n      });\n    });\n    var simTimers = [], flipped = [];\n    var simulate = function () {\n      simTimers.forEach(clearTimeout); simTimers = [];\n      flipped.forEach(function (d) { d.setAttribute(\"data-s\", \"none\"); }); flipped = []; recount();\n      if (reduce) return;\n      var queue = cells.filter(function (d) { return d.getAttribute(\"data-s\") === \"none\"; }).sort(function (a, b) { return a.getAttribute(\"data-c\") - b.getAttribute(\"data-c\"); }).slice(0, 60);\n      var t = 1800;\n      queue.forEach(function (d, i) { simTimers.push(setTimeout(function () { d.setAttribute(\"data-s\", \"man\"); flipped.push(d); recount(); }, t + i * 55)); });\n      simTimers.push(setTimeout(simulate, t + queue.length * 55 + 7000));\n    };\n    onIn[\"coverage\"] = simulate;\n    if (\"IntersectionObserver\" in window) new IntersectionObserver(function (es) { es.forEach(function (en) { if (!en.isIntersecting) { simTimers.forEach(clearTimeout); simTimers = []; } else if (!simTimers.length) { if (flipped.length) simulate(); } }); }, { threshold: 0.2 }).observe(grid);\n  }\n\n  \/* ---- cadence chart ---- *\/\n  var cad = root.querySelector(\".fotc-cadence\");\n  if (cad) {\n    var OBS = JSON.parse(cad.getAttribute(\"data-obs\")), AL = JSON.parse(cad.getAttribute(\"data-alerts\"));\n    var W = 720, Hh = 260, L = 40, R = 10, T = 26, B = 34, pw = W - L - R, ph = Hh - T - B, n = OBS.length, gap = 6, bw = (pw - gap * (n - 1)) \/ n, gmax = 50;\n    for (var g = 0; g !== gmax + 10; g += 10) { var y = T + ph - g \/ gmax * ph; el(\"line\", { x1: L, x2: L + pw, y1: y, y2: y, stroke: \"#E8E8E8\" }, cad); var tx = el(\"text\", { x: L - 8, y: y + 4, \"text-anchor\": \"end\", \"font-size\": 11, fill: \"#6B6B6B\" }, cad); tx.textContent = g; }\n    var barsC = [];\n    OBS.forEach(function (v, i) {\n      var a = AL[i], x = L + i * (bw + gap), h = v \/ gmax * ph, y0 = T + ph - h;\n      var gr = el(\"g\", { \"class\": \"fotc-bar \" + (a ? \"has-alert\" : \"no-alert\") }, cad);\n      var rect = el(\"rect\", { x: x, y: T + ph, width: bw, height: 0, rx: 2, fill: a ? \"#0097DA\" : \"#D1ECF8\" }, gr);\n      if (a) { var cy = y0 - 12; el(\"circle\", { cx: x + bw \/ 2, cy: cy, r: 7, fill: \"#E3322B\", opacity: 0 }, gr); var t2 = el(\"text\", { x: x + bw \/ 2, y: cy + 3.5, \"text-anchor\": \"middle\", \"font-size\": 9.5, \"font-weight\": 500, fill: \"#fff\", opacity: 0 }, gr); t2.textContent = a; }\n      var hit = el(\"rect\", { x: x - gap \/ 2, y: T, width: bw + gap, height: ph, fill: \"transparent\" }, gr);\n      hit.addEventListener(\"mousemove\", function (e) { showTip(e, LX.day + \" \" + (i + 1) + \" \u00b7 \" + v + \" \" + LX.per_run + \" \u00b7 \" + a + \" \" + (a === 1 ? LX.alert1 : LX.alertN)); });\n      hit.addEventListener(\"mouseleave\", hideTip);\n      if (i % 4 === 0) { var lb = el(\"text\", { x: x + bw \/ 2, y: T + ph + 18, \"text-anchor\": \"middle\", \"font-size\": 10, fill: \"#6B6B6B\" }, cad); lb.textContent = LX.day + \" \" + (i + 1); }\n      barsC.push({ rect: rect, y: y0, h: h, g: gr });\n    });\n    el(\"circle\", { cx: L + pw - 150, cy: 10, r: 5, fill: \"#E3322B\" }, cad); var lg = el(\"text\", { x: L + pw - 140, y: 14, \"font-size\": 11, fill: \"#6B6B6B\" }, cad); lg.textContent = LX.legend;\n    var grow = function () { barsC.forEach(function (b, i) { setTimeout(function () { b.rect.setAttribute(\"y\", b.y); b.rect.setAttribute(\"height\", b.h); b.g.querySelectorAll(\"circle,text\").forEach(function (m) { m.setAttribute(\"opacity\", 1); }); }, reduce ? 0 : i * 35); }); };\n    onIn[\"cadence\"] = grow;\n    var tog = root.querySelector(\"[data-only-alerts]\"); if (tog) tog.addEventListener(\"click\", function () { tog.classList.toggle(\"is-on\"); cad.classList.toggle(\"only-alerts\"); });\n    var counter = root.querySelector(\"[data-cad-counter]\");\n    if (counter) { var sumObs = OBS.reduce(function (a, b) { return a + b; }, 0) * 48, sumAl = AL.reduce(function (a, b) { return a + b; }, 0); counter.textContent = \"\u2248 \" + fmt(sumObs) + \" \" + LX.obs + \" \u00b7 \" + sumAl + \" \" + LX.alertN + \" \u00b7 \" + (100 * sumAl \/ sumObs).toFixed(2).replace(\".\", \",\") + \" %\"; }\n  }\n\n  \/* ---- baseline chart (interactive) ---- *\/\n  var bl = root.querySelector(\".fotc-baseline\");\n  if (bl) {\n    var W2 = 720, H2 = 240, L2 = 44, R2 = 12, T2 = 22, B2 = 30, pw2 = W2 - L2 - R2, ph2 = H2 - T2 - B2, N = 21;\n    var s0 = 7, rndb = function () { s0 = (s0 * 9301 + 49297) % 233280; return s0 \/ 233280; };\n    var base = []; for (var i = 0; i !== N; i++) base.push(Math.round((20 + (rndb() - 0.5) * 8) * 10) \/ 10);\n    var vmax = 2000, Y = function (v) { return T2 + ph2 - Math.sqrt(Math.max(0, v) \/ vmax) * ph2; }, X = function (i) { return L2 + i \/ (N - 1) * pw2; };\n    [0, 20, 100, 400, 1000, 2000].forEach(function (g) { var y = Y(g); el(\"line\", { x1: L2, x2: L2 + pw2, y1: y, y2: y, stroke: \"#E8E8E8\" }, bl); var t = el(\"text\", { x: L2 - 8, y: y + 4, \"text-anchor\": \"end\", \"font-size\": 11, fill: \"#6B6B6B\" }, bl); t.textContent = g; });\n    var barsB = []; for (var i = 0; i !== N; i++) barsB.push(el(\"rect\", { x: X(i) - 10, width: 20, rx: 2, fill: \"#D1ECF8\" }, bl));\n    var pMean = el(\"path\", { fill: \"none\", stroke: \"#E3322B\", \"stroke-width\": 2.5, \"stroke-linejoin\": \"round\" }, bl);\n    var pMed = el(\"path\", { fill: \"none\", stroke: \"#00A855\", \"stroke-width\": 2.5, \"stroke-linejoin\": \"round\" }, bl);\n    var lMean = el(\"text\", { \"font-size\": 11, \"font-weight\": 500, fill: \"#E3322B\" }, bl); lMean.textContent = LX.mean;\n    var lMed = el(\"text\", { \"font-size\": 11, \"font-weight\": 500, fill: \"#00A855\" }, bl); lMed.textContent = LX.median;\n    var lInc = el(\"text\", { \"text-anchor\": \"middle\", \"font-size\": 11, fill: \"#6B6B6B\" }, bl); lInc.textContent = LX.incident;\n    var ax = el(\"text\", { x: L2, y: H2 - 8, \"font-size\": 10, fill: \"#6B6B6B\" }, bl); ax.textContent = LX.day1;\n    var ax2 = el(\"text\", { x: L2 + pw2, y: H2 - 8, \"text-anchor\": \"end\", \"font-size\": 10, fill: \"#6B6B6B\" }, bl); ax2.textContent = LX.day21;\n    var zOut = root.querySelector(\"[data-z-out]\");\n    var median = function (w) { var s = w.slice().sort(function (a, b) { return a - b; }), m = Math.floor(s.length \/ 2); return s.length % 2 ? s[m] : (s[m - 1] + s[m]) \/ 2; };\n    var draw = function () {\n      var mag = +root.querySelector(\"[data-mag]\").value, win = +root.querySelector(\"[data-win]\").value;\n      var series = base.slice(); series[11] = mag; series[12] = Math.round(mag * 0.7); series[13] = Math.round(mag * 0.3);\n      var mean = [], med = [];\n      for (var i = 0; i !== N; i++) { var w = series.slice(Math.max(0, i - win), i); mean.push(!lt(w.length, 3) ? w.reduce(function (a, b) { return a + b; }, 0) \/ w.length : null); med.push(!lt(w.length, 3) ? median(w) : null); }\n      series.forEach(function (v, i) { barsB[i].setAttribute(\"y\", Y(v)); barsB[i].setAttribute(\"height\", T2 + ph2 - Y(v)); });\n      var path = function (vals) { var pts = []; vals.forEach(function (v, i) { if (v !== null) pts.push(X(i).toFixed(1) + \",\" + Y(v).toFixed(1)); }); return \"M\" + pts.join(\" L\"); };\n      pMean.setAttribute(\"d\", path(mean)); pMed.setAttribute(\"d\", path(med));\n      var li = Math.min(N - 1, 12 + Math.ceil(win \/ 2));\n      var lx = Math.min(X(li) + 6, L2 + pw2 - 62);\n      lMean.setAttribute(\"x\", lx); lMean.setAttribute(\"y\", Y(mean[li]) - 8);\n      lMed.setAttribute(\"x\", lx); lMed.setAttribute(\"y\", Y(med[li]) + 16);\n      lInc.setAttribute(\"x\", X(11)); lInc.setAttribute(\"y\", Y(mag) - 8);\n      if (zOut) {\n        var d2 = 12, w2 = series.slice(Math.max(0, d2 - win), d2), mu = w2.reduce(function (a, b) { return a + b; }, 0) \/ w2.length;\n        var sd = Math.sqrt(w2.reduce(function (a, b) { return a + (b - mu) * (b - mu); }, 0) \/ (w2.length - 1));\n        var md = median(w2), mad = median(w2.map(function (v) { return Math.abs(v - md); }));\n        var zMean = (series[d2] - mu) \/ Math.max(sd, 1), zMed = (series[d2] - md) \/ Math.max(1.4826 * mad, 0.05 * md, 1);\n        zOut.textContent = \"\"; LX.zout.split(\/(\\{[ab]\\})\/).forEach(function (part) { if (part === \"{a}\" || part === \"{b}\") { var b = document.createElement(\"b\"); b.style.color = part === \"{a}\" ? \"#E3322B\" : \"#00A855\"; b.textContent = part === \"{a}\" ? zMean.toFixed(1) : zMed.toFixed(0); zOut.appendChild(b); } else zOut.appendChild(document.createTextNode(part)); });\n      }\n      root.querySelector(\"[data-mag-out]\").textContent = fmt(mag); root.querySelector(\"[data-win-out]\").textContent = win + LX.days;\n    };\n    root.querySelectorAll(\"[data-mag],[data-win]\").forEach(function (inp) { inp.addEventListener(\"input\", draw); });\n    draw();\n  }\n\n  \/* ---- terminal typing ---- *\/\n  var term = root.querySelector(\".fotc-term\");\n  if (term) {\n    var lines = Array.prototype.slice.call(term.querySelectorAll(\".tl\")), tTimers = [], full = lines.map(function (l) { return l.innerHTML; });\n    var playTerm = function () {\n      tTimers.forEach(clearTimeout); tTimers = [];\n      if (reduce) { term.classList.remove(\"is-anim\"); return; }\n      term.classList.add(\"is-anim\");\n      lines.forEach(function (l, i) { l.classList.remove(\"is-on\", \"caret\"); l.innerHTML = full[i]; });\n      var t = 400;\n      lines.forEach(function (l, i) {\n        var isCmd = l.getAttribute(\"data-t\") === \"cmd\";\n        if (isCmd) {\n          var txt = l.textContent, n = txt.length, per = Math.max(6, Math.min(18, 1400 \/ n));\n          tTimers.push(setTimeout(function () { l.classList.add(\"is-on\", \"caret\"); l.textContent = \"\"; }, t));\n          for (var k = 1; !lt(n, k); k += 3) (function (k) { tTimers.push(setTimeout(function () { l.textContent = txt.slice(0, k); }, t + k * per)); })(k);\n          t += n * per + 250;\n          tTimers.push(setTimeout(function () { l.textContent = txt; l.classList.remove(\"caret\"); }, t)); t += 350;\n        } else {\n          tTimers.push(setTimeout(function () { l.classList.add(\"is-on\"); }, t)); t += l.classList.contains(\"gap\") ? 500 : 140;\n        }\n      });\n      tTimers.push(setTimeout(playTerm, t + 9000));\n    };\n    onIn[\"terminal\"] = playTerm;\n    if (\"IntersectionObserver\" in window) new IntersectionObserver(function (es) { es.forEach(function (en) { if (!en.isIntersecting) { tTimers.forEach(clearTimeout); tTimers = []; } else if (!tTimers.length) { if (term.classList.contains(\"is-anim\")) playTerm(); } }); }, { threshold: 0.2 }).observe(term);\n  }\n\n  \/* ---- code block ---- *\/\n  root.querySelectorAll(\".fotc-code\").forEach(function (box) {\n    var pre = box.querySelector(\"pre\"), copy = box.querySelector(\"[data-copy]\"), exp = box.querySelector(\"[data-expand]\");\n    if (copy) copy.addEventListener(\"click\", function () {\n      var txt = pre.innerText; var done = function () { copy.textContent = LX.copied; setTimeout(function () { copy.textContent = LX.copy; }, 1600); };\n      if (navigator.clipboard) navigator.clipboard.writeText(txt).then(done, done); else done();\n    });\n    if (exp) exp.addEventListener(\"click\", function () { box.classList.toggle(\"is-open\"); exp.textContent = box.classList.contains(\"is-open\") ? LX.collapse : LX.expand; });\n  });\n}\nif (document.readyState === \"loading\") document.addEventListener(\"DOMContentLoaded\", init); else init();\n})();\n<\/script>\n\n<p class=\"fotc-lead\">Dac\u0103 r\u0103spunzi de un cont de facturare Google Cloud \u00een care cineva de\u021bine o cheie API pentru un serviciu AI, acest text este pentru tine. Google a ad\u0103ugat recent \u00een Cloud Billing limitele de cheltuieli (spend caps), un mecanism care suspend\u0103 automat serviciul acoperit dup\u0103 dep\u0103\u0219irea unei sume stabilite. Combinat cu detectorul de anomalii de cost construit de FOTC (Cloud Anomaly Detector), ofer\u0103 o imagine complet\u0103 asupra finan\u021belor contului \u2013 limita p\u0103ze\u0219te suma stabilit\u0103, iar detectorul arat\u0103 dac\u0103 cheltuielile sunt normale \u0219i ce a cauzat schimbarea.<\/p>\n  <p class=\"fotc-lead\">Despre cum a ajutat Cloud Anomaly Detector la oprirea unui incident de LLMjacking \u00een c\u00e2teva ore am scris <a href=\"https:\/\/fotc.com\/ro\/blog\/cand-soseste-factura-e-deja-prea-tarziu-cum-am-construit-un-detector-de-anomalii-de-cost-pentru-cloud-2\/\">\u00een articolul anterior din aceast\u0103 serie<\/a>. Azi mergem mai \u00een profunzime \u2013 vei afla ce ofer\u0103 concret un spend cap, unde se termin\u0103 posibilit\u0103\u021bile lui \u0219i cum s\u0103 distribui limitele pe zeci de proiecte f\u0103r\u0103 riscul de a opri produc\u021bia. Prime\u0219ti \u0219i trei comenzi cu care \u00ee\u021bi verifici contul chiar azi.<\/p>\n  <div class=\"fotc-stats\" role=\"group\" aria-label=\"O lun\u0103 de lucru a detectorului \u00een cifre\">\n    <div class=\"fotc-stat\" data-reveal><b data-count=\"48\">48<\/b><span>rul\u0103ri ale detectorului pe zi, pe sute de conturi simultan<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal><b data-count=\"20\" data-prefix=\"\u2248 \">\u2248 20<\/b><span>conturi care deviaz\u0103 statistic \u00eentr-o rulare tipic\u0103<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal><b data-count=\"16\">16<\/b><span>alerte trimise oamenilor \u00een 29 de zile. Restul sunt observa\u021bii.<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal><b data-count=\"2\">2<\/b><span>alerte au necesitat localizarea sursei \u00eempreun\u0103 cu clientul<\/span><\/div>\n  <\/div>\n  <p>De la publicarea primului articol, sistemul a lucrat o lun\u0103 \u00eentreag\u0103 pe sute de conturi de facturare, a fost redenumit Cloud Anomaly Detector \u0219i a primit \u00een dashboard o vizualizare nou\u0103, dedicat\u0103 spend cap-urilor: o hart\u0103 care arat\u0103 ce proiecte au deja o limit\u0103 \u0219i care stau \u00eenc\u0103 f\u0103r\u0103 ea. \u00cencepem cu limitele, pentru c\u0103 ele schimb\u0103 raportul de for\u021be.<\/p>\n  <h2>Spend cap \u2013 cum func\u021bioneaz\u0103 mecanismul<\/h2>\n  <p>Bugetele din Cloud Billing exist\u0103 de mult, dar doar notificau. Un spend cap este un buget cu aplicare: c\u00e2nd costul estimat al unui serviciu dat, \u00eentr-un proiect dat, dep\u0103\u0219e\u0219te suma, Google opre\u0219te cererile noi c\u0103tre acel serviciu. Nimeni nu trebuie s\u0103 apese nimic. Pentru cine a v\u0103zut factura pentru trei zile de inferen\u021b\u0103 sc\u0103pat\u0103 de sub control, este o schimbare calitativ\u0103. Un cap acoper\u0103 un singur serviciu \u00eentr-un singur proiect, iar lista serviciilor acceptate este deocamdat\u0103 scurt\u0103: Gemini API, Gemini Enterprise Agent Platform (fost Vertex AI), Cloud Run \u0219i Cloud Run functions. Limita func\u021bioneaz\u0103 exclusiv pe perioad\u0103 lunar\u0103, iar din 13 octombrie 2026 \u00eentreaga func\u021bie este disponibil\u0103 general (GA). A\u0219a arat\u0103 documenta\u021bia la 23 octombrie 2026. Google anun\u021b\u0103 c\u0103 versiunile urm\u0103toare vor aduce alte servicii sub limite, iar fiecare dintre ele este \u00eenc\u0103 o zon\u0103 pe care o po\u021bi opri \u00een acest fel.<\/p>\n  <p>Cloud Anomaly Detector este un sistem construit de noi, care la fiecare jum\u0103tate de or\u0103 compar\u0103 cheltuielile fiec\u0103rui cont de facturare cu propriul istoric \u0219i alarmeaz\u0103 c\u00e2nd ceva deviaz\u0103 de la el. Singur nu opre\u0219te nimic. \u00cen schimb, r\u0103spunde la \u00eentrebarea pe care limita nu o pune niciodat\u0103: este normal\u0103 aceast\u0103 cheltuial\u0103 \u0219i de unde a venit?<\/p>\n  <figure data-reveal data-trigger=\"timeline\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">O lun\u0103 din via\u021ba unui spend cap<\/span><\/div>\n<svg class='fotc-chart fotc-timeline' viewBox='0 0 720 160' role='img' aria-labelledby='tlT tlD' font-family='Fira Sans, -apple-system, Segoe UI, Roboto, sans-serif'>\n<title id='tlT'>O lun\u0103 din via\u021ba unui spend cap<\/title>\n<desc id='tlD'>Axa \u00eencepe cu prima zi a lunii. E-mail la 50, 80 \u0219i 100 la sut\u0103 din sum\u0103; dup\u0103 100 la sut\u0103 Google opre\u0219te apelurile noi c\u0103tre serviciu \u00een proiect. Limita se ridic\u0103 singur\u0103 la \u00eenceputul lunii, p\u00e2n\u0103 atunci doar manual.<\/desc>\n<line x1='30' x2='690' y1='78' y2='78' stroke='#E8E8E8' stroke-width='8' stroke-linecap='round'\/>\n<line class='tl-fill' x1='30' x2='505.2' y1='78' y2='78' stroke='#0097DA' stroke-width='8' stroke-linecap='round' pathLength='100'\/>\n<line class='tl-stop' x1='505.2' x2='690' y1='78' y2='78' stroke='#E3322B' stroke-width='8' stroke-linecap='round' stroke-dasharray='2 10' opacity='0'\/>\n<text x='30' y='114' font-size='11' fill='#6B6B6B'>ziua 1 a lunii<\/text>\n<text x='690' y='114' text-anchor='end' font-size='11' fill='#6B6B6B'>sf\u00e2r\u0219itul lunii<\/text>\n<text class='tl-stoplabel' x='597.6' y='56' text-anchor='middle' font-size='12' font-weight='500' fill='#E3322B' opacity='0'>apeluri blocate<\/text>\n<g class='tl-mark' data-at='0.36'>\n<circle class='tl-pulse' cx='267.6' cy='78' r='9' fill='none' stroke='#FAB001' stroke-width='2' opacity='0'\/>\n<circle class='tl-dot' cx='267.6' cy='78' r='9' fill='#fff' stroke='#FAB001' stroke-width='3'\/>\n<text x='267.6' y='56' text-anchor='middle' font-size='12' font-weight='500' fill='#000000'>50 %<\/text>\n<text x='267.6' y='114' text-anchor='middle' font-size='11' fill='#6B6B6B'>e-mail<\/text><\/g>\n<g class='tl-mark' data-at='0.58'>\n<circle class='tl-pulse' cx='412.8' cy='78' r='9' fill='none' stroke='#FAB001' stroke-width='2' opacity='0'\/>\n<circle class='tl-dot' cx='412.8' cy='78' r='9' fill='#fff' stroke='#FAB001' stroke-width='3'\/>\n<text x='412.8' y='56' text-anchor='middle' font-size='12' font-weight='500' fill='#000000'>80 %<\/text>\n<text x='412.8' y='114' text-anchor='middle' font-size='11' fill='#6B6B6B'>e-mail<\/text><\/g>\n<g class='tl-mark' data-at='0.72'>\n<circle class='tl-pulse' cx='505.2' cy='78' r='9' fill='none' stroke='#E3322B' stroke-width='2' opacity='0'\/>\n<circle class='tl-dot' cx='505.2' cy='78' r='9' fill='#fff' stroke='#E3322B' stroke-width='3'\/>\n<text x='505.2' y='56' text-anchor='middle' font-size='12' font-weight='500' fill='#000000'>100 %<\/text>\n<text x='505.2' y='114' text-anchor='middle' font-size='11' fill='#6B6B6B'>e-mail + blocare<\/text><\/g>\n<circle class='tl-cursor' cx='30' cy='78' r='6' fill='#0097DA' stroke='#fff' stroke-width='2'\/>\n<\/svg>\n    <figcaption>Pragurile de 50 \u0219i 80&nbsp;% sunt doar e-mailuri c\u0103tre administratorii de facturare \u0219i proprietarii proiectului. Dup\u0103 100&nbsp;% Google blocheaz\u0103 apelurile noi, dar cererile \u00een curs se finalizeaz\u0103 \u0219i ajung pe factur\u0103. Limita se ridic\u0103 singur\u0103 la \u00eenceputul lunii urm\u0103toare. P\u00e2n\u0103 atunci poate fi ridicat\u0103 manual, iar serviciul revine complet la normal \u00een cel mult o or\u0103.<\/figcaption>\n  <\/figure>\n  <div class=\"fotc-cols\">\n    <div class=\"fotc-col fotc-col--stop\" data-reveal><h3>Ce blocheaz\u0103 cap-ul<\/h3><ul><li>Apelurile API noi c\u0103tre serviciul indicat, \u00een acel proiect<\/li><li>P\u00e2n\u0103 la \u00eenceputul lunii urm\u0103toare sau p\u00e2n\u0103 la editarea manual\u0103 a bugetului<\/li><\/ul><\/div>\n    <div class=\"fotc-col fotc-col--flow\" data-reveal><h3>Ce continu\u0103<\/h3><ul><li>Compute, storage \u0219i alte resurse persistente<\/li><li>Alte proiecte \u0219i alte servicii<\/li><li>Costul cererilor \u00een curs; aplicarea nu este instantanee<\/li><\/ul><\/div>\n  <\/div>\n  <p>Tuturor celor care au \u00een organiza\u021bie m\u0103car o cheie API pentru un serviciu AI le spunem acela\u0219i lucru: <strong>acolo unde Spend Caps sunt disponibile, pune un cap pe fiecare proiect \u00een care serviciul este activat \u0219i pe fiecare \u00een care nu este<\/strong>. Al doilea caz este cel mai important. Incidentul din mai, pe care l-am descris \u00een primul articol, a constat \u00een faptul c\u0103 un atacator a activat un serviciu AI \u00eentr-un proiect care nu \u00eel folosise niciodat\u0103. Un cap de o sut\u0103 de euro pe un proiect care nu are de ce s\u0103 cheltuiasc\u0103 un cent pe AI ajut\u0103 la limitarea costului unui astfel de abuz. Suma trebuie aleas\u0103 con\u0219tient, \u021bin\u00e2nd cont de \u00eent\u00e2rzierea aplic\u0103rii.<\/p>\n  <h2>Limita pe proiect \u0219i monitorizarea \u00eentregului cont<\/h2>\n  <p>Un spend cap protejeaz\u0103 o pereche proiect&nbsp;\u00d7&nbsp;serviciu. Monitorizarea \u00eentregului cont acoper\u0103 \u0219i costurile proiectelor nou ata\u0219ate. Configura\u021bia implicit\u0103 a Google Cloud acord\u0103 rolul Project Creator tuturor utilizatorilor din domeniu, iar Billing Account User este \u00eemp\u0103r\u021bit mai larg dec\u00e2t \u00ee\u0219i aminte\u0219te cineva. Cu astfel de drepturi, un script poate crea un proiect nou, ata\u0219a facturarea \u0219i porni un serviciu AI. Toate cap-urile \u0219i bugetele tale de proiect stau \u00een proiectele vechi.<\/p>\n  <figure data-reveal data-trigger=\"attack\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Calea prin proiectul nou \u0219i trei straturi de control<\/span><\/div>\n<svg class='fotc-chart fotc-attack' viewBox='0 0 720 330' role='img' aria-labelledby='atT atD' font-family='Fira Sans, -apple-system, Segoe UI, Roboto, sans-serif'>\n<title id='atT'>Calea prin proiectul nou \u0219i trei straturi de control<\/title>\n<desc id='atD'>Atacatorul creeaz\u0103 un proiect nou, \u00eei ata\u0219eaz\u0103 facturarea \u0219i activeaz\u0103 un serviciu AI. Spend cap-urile \u0219i bugetele de proiect acoper\u0103 proiectele pe care le indic\u0103. Detec\u021bia la nivelul contului de facturare acoper\u0103 \u0219i proiectele noi.<\/desc>\n<defs><marker id='arr' viewBox='0 0 10 10' refX='9' refY='5' markerWidth='8' markerHeight='8' orient='auto-start-reverse'><path d='M0,0 L10,5 L0,10 z' fill='#6B6B6B'\/><\/marker><\/defs>\n<g class='at-step at-step-0'><rect x='20' y='20' width='250' height='56' rx='12'\/><text class='t' x='34' y='46' font-size='13' font-weight='500'>Creden\u021biale scurse<\/text><text class='s' x='34' y='66' font-size='11.5'>un rol cu drept de creare a proiectelor<\/text><\/g>\n<line class='at-link at-link-0' x1='145' x2='145' y1='76' y2='92' stroke='#6B6B6B' stroke-width='1.5' marker-end='url(#arr)'\/>\n<g class='at-step at-step-1'><rect x='20' y='92' width='250' height='56' rx='12'\/><text class='t' x='34' y='118' font-size='13' font-weight='500'>Proiect nou<\/text><text class='s' x='34' y='138' font-size='11.5'>Project Creator, adesea implicit<\/text><\/g>\n<line class='at-link at-link-1' x1='145' x2='145' y1='148' y2='164' stroke='#6B6B6B' stroke-width='1.5' marker-end='url(#arr)'\/>\n<g class='at-step at-step-2'><rect x='20' y='164' width='250' height='56' rx='12'\/><text class='t' x='34' y='190' font-size='13' font-weight='500'>Facturare ata\u0219at\u0103<\/text><text class='s' x='34' y='210' font-size='11.5'>Billing Account User<\/text><\/g>\n<line class='at-link at-link-2' x1='145' x2='145' y1='220' y2='236' stroke='#6B6B6B' stroke-width='1.5' marker-end='url(#arr)'\/>\n<g class='at-step at-step-3'><rect x='20' y='236' width='250' height='56' rx='12'\/><text class='t' x='34' y='262' font-size='13' font-weight='500'>API serviciu AI + cheie<\/text><text class='s' x='34' y='282' font-size='11.5'>consum din primul minut<\/text><\/g>\n<path class='at-path at-path-0 at-miss' d='M270,264 C 350,264 350,78 430,78' fill='none' stroke-width='2' pathLength='100' marker-end='url(#arr)'\/>\n<g class='at-layer at-layer-0 at-miss'><rect x='430' y='48' width='270' height='60' rx='12'\/><text class='t' x='444' y='74' font-size='13' font-weight='500'>Spend cap<\/text><text class='s' x='444' y='94' font-size='11.5'>proiectul \u0219i serviciul indicate<\/text><\/g>\n<text class='at-verdict at-verdict-0 at-miss' x='350' y='70' text-anchor='middle' font-size='11' font-weight='500'>impune suma<\/text>\n<path class='at-path at-path-1 at-miss' d='M270,264 C 350,264 350,168 430,168' fill='none' stroke-width='2' pathLength='100' marker-end='url(#arr)'\/>\n<g class='at-layer at-layer-1 at-miss'><rect x='430' y='138' width='270' height='60' rx='12'\/><text class='t' x='444' y='164' font-size='13' font-weight='500'>Buget de proiect<\/text><text class='s' x='444' y='184' font-size='11.5'>proiectul indicat<\/text><\/g>\n<text class='at-verdict at-verdict-1 at-miss' x='350' y='160' text-anchor='middle' font-size='11' font-weight='500'>alerteaz\u0103 despre sum\u0103<\/text>\n<path class='at-path at-path-2 at-hit' d='M270,264 C 350,264 350,258 430,258' fill='none' stroke-width='2' pathLength='100' marker-end='url(#arr)'\/>\n<g class='at-layer at-layer-2 at-hit'><rect x='430' y='228' width='270' height='60' rx='12'\/><text class='t' x='444' y='254' font-size='13' font-weight='500'>Detec\u021bie pe cont<\/text><text class='s' x='444' y='274' font-size='11.5'>\u0219i proiectele noi<\/text><\/g>\n<text class='at-verdict at-verdict-2 at-hit' x='350' y='250' text-anchor='middle' font-size='11' font-weight='500'>evalueaz\u0103 \u0219i escaladeaz\u0103<\/text>\n<circle class='at-runner' cx='145' cy='20' r='6' fill='#E3322B' opacity='0'\/>\n<\/svg>\n    <figcaption>Cap-ul \u0219i bugetul de proiect acoper\u0103 proiectul pe care \u00eel indic\u0103. Monitorizarea contului de facturare acoper\u0103 \u0219i costurile proiectelor noi, de \u00eendat\u0103 ce apar \u00een datele de facturare.<\/figcaption>\n  <\/figure>\n  <p><strong>\u00centr-una dintre intruziunile la care am fost chema\u021bi s\u0103 ajut\u0103m, atacatorul a creat un proiect nou \u0219i l-a ata\u0219at contului de facturare al victimei.<\/strong> Clientul a v\u0103zut o factur\u0103 pe care nu \u0219i-o putea explica \u0219i ne-a cerut ajutorul. Prima \u00eentrebare nu a fost \u201ecine&#8221;, ci \u201ecare proiect&#8221;. P\u00e2n\u0103 c\u00e2nd un cost nu are o adres\u0103, \u00een jurnale nu ai ce c\u0103uta.<\/p>\n  <p>Odat\u0103 ce adresa era cunoscut\u0103, restul l-au spus jurnalele de audit. Mediul \u00een care cre\u0219tea costul nu apar\u021binea niciunui lucru pe care clientul s\u0103 \u00eel recunoasc\u0103, pentru c\u0103 cineva \u00eel crease de la zero \u0219i \u00eel ata\u0219ase contului lui de facturare. De aceea merit\u0103 urm\u0103rite schimb\u0103rile de pe \u00eentregul cont, nu doar din proiectele cunoscute. O linie nou\u0103 de cost care cheltuie\u0219te din prima or\u0103 este pentru detector un tip separat de anomalie.<\/p>\n  <p>De aici regula adoptat\u0103 pentru \u00eentregul portofoliu: <strong>spend cap-ul impune suma stabilit\u0103, iar detec\u021bia la nivelul contului de facturare recunoa\u0219te schimb\u0103rile neobi\u0219nuite, inclusiv \u00een proiecte noi<\/strong>. La acestea se adaug\u0103 dou\u0103 schimb\u0103ri IAM ieftine: retragerea rolului implicit Project Creator de la \u00eentregul domeniu \u0219i verificarea celor care de\u021bin cu adev\u0103rat Billing Account User.<\/p>\n  <h3>C\u00e2nd sunt de ajuns limitele \u0219i o verificare manual\u0103<\/h3>\n  <p>Nu orice mediu are nevoie de un detector. Dac\u0103 ai c\u00e2teva proiecte, o echip\u0103 \u0219i ai retras domeniului rolul Project Creator, atunci limitele pe perechile proiect&nbsp;\u00d7&nbsp;serviciu AI acceptate, un buget pe \u00eentregul cont \u0219i o verificare regulat\u0103 a cheltuielilor \u00een consol\u0103 sunt de ajuns pentru controlul zilnic al costurilor. Lista proiectelor \u00eencape pe un ecran, iar fiecare linie nou\u0103 de pe factur\u0103 va fi recunoscut\u0103 de cineva dup\u0103 nume. Sfatul \u00eenceteaz\u0103 s\u0103 func\u021bioneze c\u00e2nd proiectele sunt zeci, iar lista celor cu drept de a crea proiecte este mai lung\u0103 dec\u00e2t lista celor care \u0219tiu asta. Atunci nimeni nu mai \u021bine \u00eentregul cont \u00een minte \u0219i e nevoie de un sistem care s\u0103 \u00eel \u021bin\u0103 minte \u00een locul oamenilor. Despre asta este restul textului.<\/p>\n  <h2>Cum am abordat cap-urile la scar\u0103<\/h2>\n  <p>La sute de conturi \u0219i mii de proiecte ai nevoie de o hart\u0103. Am construit-o ca a doua fil\u0103 a dashboard-ului detectorului. C\u00e2teva decizii luate atunci nu depind de scar\u0103 \u0219i func\u021bioneaz\u0103 la fel cu c\u00e2teva proiecte ca \u0219i cu mii.<\/p>\n  <figure data-reveal data-trigger=\"coverage\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Harta acoperirii: perechi proiect&nbsp;\u00d7&nbsp;serviciu AI<\/span>\n      <div class=\"fotc-controls\">\n        <button type=\"button\" class=\"fotc-btn fotc-btn--dot\" style=\"--dot:#00A855\" data-filter=\"api\">API<\/button>\n        <button type=\"button\" class=\"fotc-btn fotc-btn--dot\" style=\"--dot:#FAB001\" data-filter=\"man\">declarat<\/button>\n        <button type=\"button\" class=\"fotc-btn fotc-btn--dot\" style=\"--dot:#D1D1D1\" data-filter=\"none\">f\u0103r\u0103 limit\u0103<\/button>\n      <\/div>\n    <\/div>\n    <div class=\"fotc-grid\" aria-hidden=\"true\"><\/div>\n    <div class=\"fotc-bars\" aria-hidden=\"true\"><\/div>\n    <div class=\"fotc-legend\"><span><b data-n=\"api\">0<\/b> confirmate prin API<\/span><span><b data-n=\"man\">0<\/b> declarate de un om<\/span><span><b data-n=\"none\">0<\/b> f\u0103r\u0103 limit\u0103<\/span><\/div>\n    <figcaption>Fiecare p\u0103trat este o pereche proiect&nbsp;\u00d7&nbsp;serviciu AI. Numitorul este fiecare proiect ata\u0219at contului, inclusiv cele f\u0103r\u0103 istoric de utilizare a AI. Verdele \u0219i galbenul nu se adun\u0103 niciodat\u0103 \u00eentr-un singur procent. Aranjament ilustrativ, nu captur\u0103 din produc\u021bie; anima\u021bia arat\u0103 ordinea lucrului, de la cele mai mici medii.<\/figcaption>\n  <\/figure>\n  <p>\u00cen primul r\u00e2nd, ne-am concentrat pe numitor, adic\u0103 pe ce intr\u0103 \u00een calculul acoperirii. Num\u0103r\u0103m fiecare proiect ata\u0219at contului, inclusiv pe cele care nu au atins niciodat\u0103 AI. Dac\u0103 am num\u0103ra doar proiectele care folosesc deja AI, harta ar lumina verde \u0219i nu ar spune nimic despre risc. Protejezi ceea ce atacatorul urmeaz\u0103 s\u0103 activeze.<\/p>\n  <p>\u00cen al doilea r\u00e2nd, am stabilit c\u0103 un buget f\u0103r\u0103 filtru de serviciu nu este o limit\u0103, ci o alert\u0103, \u0219i nu are voie s\u0103 intre \u00een acoperire. C\u00e2nd am num\u0103rat naiv, acoperirea a ie\u0219it de zeci de ori mai bun\u0103 dec\u00e2t era \u00een realitate.<\/p>\n  <p>\u00cen al treilea r\u00e2nd, am stabilit de unde vine suma. O propune istoricul contului: cea mai mare dintre dou\u0103 valori, media lunilor complete \u0219i ultima lun\u0103 complet\u0103. Luna curent\u0103 este exclus\u0103, pentru c\u0103 o limit\u0103 calculat\u0103 la jum\u0103tatea lunii este calculat\u0103 din jum\u0103tate de trafic. Rotunjim mereu \u00een sus, fiindc\u0103 o limit\u0103 rotunjit\u0103 \u00een jos st\u0103 sub traficul pentru care a fost dimensionat\u0103. Un proiect f\u0103r\u0103 cheltuieli prime\u0219te o sum\u0103 simbolic\u0103.<\/p>\n  <p>\u00cen al patrulea r\u00e2nd, am stabilit ordinea. Lucr\u0103m lista de la cele mai mici medii, pentru c\u0103 acolo o gre\u0219eal\u0103 cost\u0103 cel mai pu\u021bin \u0219i acolo sunt cele mai multe proiecte f\u0103r\u0103 istoric. Mediile mari, cu trafic real pe AI, primesc limit\u0103 doar dup\u0103 o discu\u021bie despre sum\u0103. O limit\u0103 aleas\u0103 gre\u0219it opre\u0219te produc\u021bia clientului la fel de eficient ca un atac.<\/p>\n  <p>\u0218i regula pe care nu o \u00eenc\u0103lc\u0103m: ce a confirmat API-ul \u0219i ce a bifat un om manual \u00een consol\u0103 au culori separate pe hart\u0103 \u0219i nu se \u00eensumeaz\u0103 niciodat\u0103 \u00eentr-un singur procent. Declara\u021bia este valoroas\u0103 pentru c\u0103 poart\u0103 un nume \u0219i o dat\u0103. Dovad\u0103 este doar citirea.<\/p>\n  <h2>O lun\u0103 \u00een produc\u021bie: \u00eentre \u201edeviaz\u0103&#8221; \u0219i \u201emerit\u0103 s\u0103 trezim pe cineva&#8221;<\/h2>\n  <p>Detectorul compar\u0103 fiecare cont doar cu propriul istoric, a\u0219a c\u0103 \u00een fiecare rulare zeci de conturi deviaz\u0103 de la norm\u0103, \u0219i a\u0219a trebuie s\u0103 fie. Toat\u0103 valoarea sistemului st\u0103 \u00een felul \u00een care alege, din aceste observa\u021bii, cele c\u00e2teva zeci pe lun\u0103 la care are dreptul s\u0103 \u00eentrerup\u0103 ziua cuiva.<\/p>\n  <figure data-reveal data-trigger=\"cadence\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\" data-cad-counter>observa\u021bii versus alerte<\/span><div class=\"fotc-controls\"><button type=\"button\" class=\"fotc-btn\" data-only-alerts>Doar zilele cu alerte<\/button><\/div><\/div>\n    <svg class=\"fotc-chart fotc-cadence\" viewBox=\"0 0 720 260\" role=\"img\" aria-labelledby=\"cadT cadD\" data-obs=\"[21, 15, 20, 13, 40, 46, 21, 17, 17, 17, 14, 13, 13, 9, 18, 23, 15, 6, 5, 13, 17, 23, 23, 12, 4, 4, 15, 13, 17]\" data-alerts=\"[0, 0, 5, 1, 3, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]\" font-family=\"Fira Sans, -apple-system, Segoe UI, Roboto, sans-serif\">\n      <title id=\"cadT\">Conturi deviante per rulare \u0219i alerte trimise, 29 de zile<\/title>\n      <desc id=\"cadD\">Bare de la 4 la 46 de conturi per rulare; alerte doar \u00een zece dintre zile, \u0219aisprezece \u00een total.<\/desc>\n    <\/svg>\n    <figcaption>Num\u0103rul mediu de conturi deviante per rulare (bare) \u0219i alertele trimise oamenilor (marcaje ro\u0219ii). 29 de zile consecutive, august \u0219i septembrie 2026, din jurnalele detectorului. Zilele cu cele mai multe observa\u021bii nu au fost zilele cu alerte.<\/figcaption>\n  <\/figure>\n  <p>Un caz concret din 3 septembrie. Un cont care de obicei cheltuie\u0219te vreo zece \u0219i ceva de euro pe zi a cheltuit 500 \u00eentr-o singur\u0103 zi. Multiplicator \u00d723, cre\u0219tere de 2200 la sut\u0103, practic totul pe un singur serviciu de h\u0103r\u021bi. Detectorul l-a v\u0103zut la 03:00 \u0219i l-a confirmat de 42 de ori \u00een urm\u0103toarele 21 de ore. Nu a trezit pe nimeni, pentru c\u0103 500 de euro nu trec pragul valoric de la care avem voie s\u0103 \u00eentrerupem ziua cuiva.<\/p>\n  <p>Alerta a plecat totu\u0219i a doua zi diminea\u021ba, dar de la alt detector. Prognoza pentru sf\u00e2r\u0219itul lunii a ajuns la 6000 de euro fa\u021b\u0103 de 900 \u00een lunile precedente. La scara unei zile suma era prea mic\u0103 pentru a trezi pe cineva. La scara unei luni, nu.<\/p>\n  <p>Nu a fost un atac. La client se stricase partea de h\u0103r\u021bi, traficul se comutase pe un proiect de rezerv\u0103 \u0219i \u00eencepuse s\u0103 genereze cost acolo unde nu se uita nimeni. O defec\u021biune, nu o spargere, dar factura ar fi crescut la fel de mult timp c\u00e2t nu s-ar fi uitat nimeni.<\/p>\n  <h3>Alerta trebuie s\u0103 arate cu degetul<\/h3>\n  <p>Dou\u0103 dintre cele \u0219aisprezece alerte s-au \u00eencheiat cu o sesiune comun\u0103 cu clientul. Am\u00e2ndou\u0103 ar\u0103tau la fel: cardul spunea c\u0103 acel cont cheltuie\u0219te de mai multe ori mai mult dec\u00e2t de obicei, iar de partea cealalt\u0103 nimeni nu putea spune din ce proiect provine acest cost \u0219i ce cheie st\u0103 \u00een spate. Un cont de facturare este o adres\u0103 prea grosier\u0103 ca s\u0103 faci ceva cu ea.<\/p>\n  <p>Detec\u021bia a cobor\u00e2t deci mai jos, la un singur proiect \u0219i un singur SKU, adic\u0103 cea mai m\u0103runt\u0103 linie pe care Google o trece pe factur\u0103. Serviciile de pe card sunt ordonate dup\u0103 suma cu care au crescut efectiv, nu dup\u0103 procente. Un serviciu care a s\u0103rit de la un cent la \u0219apte euro a crescut cu \u0219apte sute la sut\u0103 \u0219i nu \u00eenseamn\u0103 nimic. Unul care a crescut cu \u0219apte mii de euro este \u00eentregul eveniment.<\/p>\n  <p>Defalcarea se calculeaz\u0103 \u00een aceea\u0219i rulare ca restul cardului, iar acesta nu este un detaliu de implementare. Exportul de facturare completeaz\u0103 fereastra \u00eenc\u0103 ore bune dup\u0103 alert\u0103, a\u0219a c\u0103 serviciile calculate la deschiderea cardului ar veni dintr-o stare mai t\u00e2rzie a datelor dec\u00e2t suma din antet. Cardul trebuie s\u0103 fie fotografia unui singur moment, chiar dac\u0103 acel moment este incomplet.<\/p>\n  <h3>Un baseline care rezist\u0103 la ce m\u0103soar\u0103<\/h3>\n  <p>\u00cent\u00e2i f\u0103r\u0103 matematic\u0103. Detectorul r\u0103spunde zilnic la o singur\u0103 \u00eentrebare: costul de azi este normal pentru acest cont? Are nevoie de dou\u0103 numere: care este norma \u0219i cu c\u00e2t se abate de obicei o zi de la ea. Problema \u00eencepe c\u00e2nd \u00een ultima s\u0103pt\u0103m\u00e2n\u0103 a existat deja o zi proast\u0103.<\/p>\n  <p>Un exemplu. Un cont cheltuie\u0219te cam 20 de euro pe zi. Luni cineva ruleaz\u0103 inferen\u021b\u0103 de 900, mar\u021bi de 640. Lunea o prinde orice metod\u0103, pentru c\u0103 900 fa\u021b\u0103 de 20 este alt\u0103 lume. Interesant\u0103 este mar\u021bea.<\/p>\n  <p><strong>Cu media.<\/strong> Media ultimelor \u0219apte zile, inclusiv lunea de 900, este 146. Dispersia obi\u0219nuit\u0103, calculat\u0103 din acelea\u0219i zile, a crescut de la 2 la peste 300, pentru c\u0103 lunea a \u00eentins-o \u00een sus. Mar\u021bea de 640 st\u0103 la mai pu\u021bin de dou\u0103 astfel de dispersii de noua norm\u0103. Pentru statistic\u0103 este o zi obi\u0219nuit\u0103. Alarma se stinge \u00een mijlocul incidentului.<\/p>\n  <p><strong>Cu mediana.<\/strong> Mediana este valoarea din mijloc dup\u0103 sortare. Cele \u0219apte zile sunt 17, 19, 20, 21, 22, 23 \u0219i 900; cea din mijloc este 21. Lunea st\u0103 la cap\u0103tul listei \u0219i nu are niciun cuv\u00e2nt de spus. Dispersia folose\u0219te acela\u0219i truc: lu\u0103m distan\u021ba fiec\u0103rei zile fa\u021b\u0103 de 21 \u0219i din nou pe cea din mijloc, adic\u0103 2. Acesta este MAD, mediana abaterilor. Mar\u021bea de 640 este la c\u00e2teva sute de astfel de dispersii de norm\u0103. Alarma continu\u0103, a\u0219a cum trebuie.<\/p>\n  <div class=\"fotc-table-scroll\">\n  <table>\n    <thead><tr><th><\/th><th>Medie<\/th><th>Median\u0103<\/th><\/tr><\/thead>\n    <tbody>\n      <tr><td>Norma dup\u0103 luni<\/td><td>146<\/td><td>21<\/td><\/tr>\n      <tr><td>Dispersia obi\u0219nuit\u0103<\/td><td>peste 300<\/td><td>2<\/td><\/tr>\n      <tr><td>Mar\u021bi cu 640<\/td><td>aproape o zi obi\u0219nuit\u0103<\/td><td>alarm\u0103<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n  <\/div>\n  <p>Dispersia are un minim, pentru c\u0103 un cont care cheltuie\u0219te exact 20, 20, 20 ar avea dispersie zero \u0219i fiecare cent ar p\u0103rea o catastrof\u0103. Iar o zi f\u0103r\u0103 r\u00e2nd \u00een export se num\u0103r\u0103 drept cost zero, ca o s\u0103pt\u0103m\u00e2n\u0103 lini\u0219tit\u0103 s\u0103 nu dispar\u0103 din istoric. Glisoarele de sub grafic arat\u0103 ambele linii pe aceea\u0219i serie.<\/p>\n  <figure data-reveal>\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Medie versus median\u0103 dup\u0103 un incident<\/span>\n      <div class=\"fotc-controls\">\n        <label class=\"fotc-range\">m\u0103rimea incidentului <input type=\"range\" min=\"100\" max=\"2000\" step=\"50\" value=\"900\" data-mag><output data-mag-out>900<\/output><\/label>\n        <label class=\"fotc-range\">fereastr\u0103 <input type=\"range\" min=\"5\" max=\"14\" step=\"1\" value=\"7\" data-win><output data-win-out>7 zile<\/output><\/label>\n      <\/div>\n    <\/div>\n    <svg class=\"fotc-chart fotc-baseline\" viewBox=\"0 0 720 240\" role=\"img\" aria-labelledby=\"blT blD\" font-family=\"Fira Sans, -apple-system, Segoe UI, Roboto, sans-serif\">\n      <title id=\"blT\">Media mobil\u0103 versus mediana dup\u0103 un incident de trei zile<\/title>\n      <desc id=\"blD\">Cost zilnic \u00een jur de 20, cu trei zile de incident. Media pe fereastr\u0103 sare \u0219i se preface a fi noua norm\u0103 pe toat\u0103 lungimea ferestrei; mediana r\u0103m\u00e2ne la 20.<\/desc>\n    <\/svg>\n    <p class=\"fotc-zout\" data-z-out><\/p>\n    <figcaption>Aceea\u0219i serie de costuri, dou\u0103 baseline-uri. Dup\u0103 incident media se d\u0103 drept noua norm\u0103 pe toat\u0103 lungimea ferestrei, a\u0219a c\u0103 zilele urm\u0103toare ale incidentului par lini\u0219tite. Mediana r\u0103m\u00e2ne la dou\u0103zeci. Ilustrare a mecanismului, ax\u0103 r\u0103d\u0103cin\u0103 p\u0103trat\u0103.<\/figcaption>\n  <\/figure>\n  <p>Dou\u0103 capcane al\u0103turi. Ziua curent\u0103: la \u0219ase diminea\u021ba contul are \u0219ase ore \u00een spate, a\u0219a c\u0103 fa\u021b\u0103 de zile \u00eentregi pare mereu o sc\u0103dere. Compar\u0103m deci cele \u0219ase ore de azi cu acelea\u0219i \u0219ase ore din zilele precedente. Detec\u021bia orar\u0103: dac\u0103 ora evaluat\u0103 st\u0103 \u00een acela\u0219i e\u0219antion din care \u00eei calculezi norma, ea \u00eens\u0103\u0219i ridic\u0103 norma \u0219i nu se abate niciodat\u0103 suficient ca s\u0103 treac\u0103 pragul. O or\u0103 de jum\u0103tate de milion pare atunci u\u0219or ridicat\u0103. Ora evaluat\u0103 trebuie scoas\u0103 din e\u0219antion.<\/p>\n  <h2>Sistemul trebuie s\u0103 \u0219tie ce nu vede<\/h2>\n  <p>Cea mai periculoas\u0103 stare a unui detector de anomalii este t\u0103cerea care arat\u0103 ca lini\u0219tea.<\/p>\n  <p>Pe 25 august exportul de facturare a \u00eencetat s\u0103 scrie. Nu o singur\u0103 surs\u0103, ci toate, una dup\u0103 alta, la c\u00e2teva zeci de minute distan\u021b\u0103. Detectorul a continuat s\u0103 ruleze din jum\u0103tate \u00een jum\u0103tate de or\u0103, a scanat tabele neschimbate de diminea\u021b\u0103 \u0219i a raportat de fiecare dat\u0103 c\u0103 nu exist\u0103 anomalii. Timp de \u0219ase ore nimeni nu se uita la flot\u0103, iar singura urm\u0103 a acestei st\u0103ri era o band\u0103 pe dashboard la care cineva trebuia s\u0103 se uite din proprie ini\u021biativ\u0103.<\/p>\n  <p>Am reparat asta \u00een aceea\u0219i zi \u0219i am exagerat imediat: o surs\u0103 moart\u0103 a \u00eenceput s\u0103 trezeasc\u0103 inginerul de gard\u0103. \u00cen noaptea aceea, la scurt timp dup\u0103 miezul nop\u021bii, cel de gard\u0103 a primit un apel despre un tabel de export \u00eenghe\u021bat, cu care oricum nu putea face nimic p\u00e2n\u0103 diminea\u021ba. A doua zi am scos calea asta. Pagerul este acum rezervat costului, iar defec\u021biunea unei surse are propriul canal \u0219i un prag num\u0103rat \u00een ore de t\u0103cere, pentru c\u0103 o surs\u0103 care p\u00e2lp\u00e2ie un sfert de or\u0103 nu este un incident, iar una care tace o jum\u0103tate de zi este.<\/p>\n  <p>Patru st\u0103ri pe care codul naiv le raporteaz\u0103 identic drept \u201enicio anomalie&#8221;:<\/p>\n  <div class=\"fotc-cards\">\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag\">sursa s-a oprit<\/span><h3>Exportul nu mai scrie de ore<\/h3><p>Detectorul scaneaz\u0103 tabele \u00eenghe\u021bate \u0219i raporteaz\u0103 succes. E nevoie de o metric\u0103 separat\u0103: de c\u00e2t timp nu a mai scris sursa nimic, cu un prag num\u0103rat \u00een ore de t\u0103cere.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag\">eroare \u00een loc de rezultat<\/span><h3>Interogarea a picat<\/h3><p>O list\u0103 goal\u0103 pentru c\u0103 interogarea a picat \u0219i o list\u0103 goal\u0103 pentru c\u0103 nu s-a g\u0103sit nimic sunt dou\u0103 fapte diferite, iar codul trebuie s\u0103 le deosebeasc\u0103. O rulare care nu a v\u0103zut flota se \u00eencheie cu eroare, nu cu raport.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag\">nu se poate judeca<\/span><h3>Cont f\u0103r\u0103 o lun\u0103 \u00eentreag\u0103 de istoric<\/h3><p>Prognoza nu se produce, \u0219i pe bun\u0103 dreptate, pentru c\u0103 nu are din ce. Dar contul este num\u0103rat \u0219i vizibil ca \u201eneevaluat&#8221;, cu data de la care va \u00eencepe s\u0103 fie evaluat.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag\">snapshot gol<\/span><h3>Zero conturi \u00een loc de sute<\/h3><p>Un rezultat gol \u00eenseamn\u0103 e\u0219ecul rul\u0103rii. O flot\u0103 goal\u0103 apare doar la prima implementare, iar atunci trebuie declarat\u0103 explicit.<\/p><\/div>\n  <\/div>\n  <div class=\"fotc-callout\" data-reveal><p><strong>Lipsa datelor nu este zero.<\/strong> Fiecare cale care nu poate dovedi c\u0103 a privit datele \u00eencheie rularea cu o eroare vizibil\u0103 \u00een monitorizare. Alternativa este un dashboard verde peste o flot\u0103 la care nu se uit\u0103 nimeni.<\/p><\/div>\n  <h2>Cum verifici la tine<\/h2>\n  <p>Trei comenzi r\u0103spund la \u00eentrebarea acestui text: poate ap\u0103rea sub contul t\u0103u de facturare un proiect de care nu \u0219tie nimeni? Prima listeaz\u0103 proiectele ata\u0219ate contului cu data cre\u0103rii, cele mai noi sus. A doua arat\u0103 cine poate crea proiecte \u00een organiza\u021bie. A treia, cine le poate ata\u0219a la facturare.<\/p>\n  <figure data-reveal data-trigger=\"terminal\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Trei \u00eentreb\u0103ri pentru contul de facturare<\/span><\/div>\n    <div class=\"fotc-term\" role=\"img\" aria-label=\"Terminal cu trei comenzi gcloud: proiectele ata\u0219ate contului cu data cre\u0103rii, persoanele cu rolul Project Creator \u00een organiza\u021bie, persoanele cu rolul Billing Account User. Eviden\u021biate: un proiect creat ieri \u0219i un \u00eentreg domeniu cu drept de creare a proiectelor.\">\n      <div class=\"fotc-term-bar\"><i><\/i><i><\/i><i><\/i><span>cont de facturare \u00b7 date exemplu<\/span><\/div>\n      <pre class=\"fotc-term-body\"><span class=\"tl cmd\" data-t=\"cmd\">$ gcloud billing projects list --billing-account=0A1B2C-3D4E5F-6A7B8C --format=\"value(projectId)\" \\\n    | xargs -I{} gcloud projects describe {} --format=\"csv[no-heading](projectId,createTime.date('%Y-%m-%d'))\" \\\n    | sort -t, -k2 -r<\/span><span class=\"tl hl-red\" data-t=\"out\">gen-ai-tmp-4471,2026-09-10          <b>\u2190 creat ieri, zero istoric de costuri<\/b><\/span><span class=\"tl\" data-t=\"out\">shop-prod,2023-04-12<\/span><span class=\"tl\" data-t=\"out\">shop-staging,2023-04-12<\/span><span class=\"tl\" data-t=\"out\">data-warehouse,2022-11-03<\/span><span class=\"tl\" data-t=\"out\">mobile-backend,2021-06-30<\/span><span class=\"tl gap\" data-t=\"out\"> <\/span><span class=\"tl cmd\" data-t=\"cmd\">$ gcloud organizations get-iam-policy 123456789012 --flatten=\"bindings[].members\" \\\n    --filter=\"bindings.role:roles\/resourcemanager.projectCreator\" --format=\"value(bindings.members)\"<\/span><span class=\"tl hl-amber\" data-t=\"out\">domain:example.com                  <b>\u2190 \u00eentregul domeniu poate crea proiecte (implicit)<\/b><\/span><span class=\"tl\" data-t=\"out\">group:cloud-admins@example.com<\/span><span class=\"tl gap\" data-t=\"out\"> <\/span><span class=\"tl cmd\" data-t=\"cmd\">$ gcloud billing accounts get-iam-policy 0A1B2C-3D4E5F-6A7B8C --flatten=\"bindings[].members\" \\\n    --filter=\"bindings.role:roles\/billing.user\" --format=\"value(bindings.members)\"<\/span><span class=\"tl\" data-t=\"out\">group:cloud-admins@example.com<\/span><span class=\"tl hl-amber\" data-t=\"out\">user:dev-2019-contractor@example.com <b>\u2190 o cheie de ani buni, nimeni nu mai \u0219tie de ce<\/b><\/span><span class=\"tl hl-amber\" data-t=\"out\">serviceAccount:ci-legacy@shop-prod.iam.gserviceaccount.com<\/span><\/pre>\n    <\/div>\n    <figcaption>Terminalul arat\u0103 date exemplu. Un proiect cu data de ieri \u0219i f\u0103r\u0103 istoric de costuri trebuie verificat \u2013 are un proprietar \u0219i limite proprii? O intrare <code>domain:<\/code> la Project Creator \u00eenseamn\u0103 configura\u021bia implicit\u0103 Google Cloud: oricine din companie poate crea un proiect.<\/figcaption>\n  <\/figure>\n  <p>Acelea\u0219i trei comenzi, gata de lipit. Completezi cele dou\u0103 variabile de sus \u0219i lipe\u0219ti restul neschimbat. Identificatorul contului \u00eel g\u0103se\u0219ti cu <code>gcloud billing accounts list<\/code>, num\u0103rul organiza\u021biei cu <code>gcloud organizations list<\/code>.<\/p>\n  <div class=\"fotc-code\" data-reveal>\n    <div class=\"fotc-code-bar\"><button type=\"button\" class=\"fotc-btn\" data-copy>Copiaz\u0103<\/button><\/div>\n<pre><code class=\"language-bash\">export BILLING_ACCOUNT_ID=\"0X0X0X-0X0X0X-0X0X0X\"   # gcloud billing accounts list\nexport ORG_ID=\"123456789012\"                       # gcloud organizations list\necho \"== 1. Proiecte ata\u0219ate contului $BILLING_ACCOUNT_ID, cele mai noi sus ==\"\ngcloud billing projects list --billing-account=\"$BILLING_ACCOUNT_ID\" --format=\"value(projectId)\" \\\n  | xargs -I{} gcloud projects describe {} --format=\"csv[no-heading](projectId,createTime.date('%Y-%m-%d'))\" \\\n  | sort -t, -k2 -r\necho; echo \"== 2. Cine poate crea proiecte \u00een organiza\u021bia $ORG_ID ==\"\ngcloud organizations get-iam-policy \"$ORG_ID\" --flatten=\"bindings[].members\" \\\n  --filter=\"bindings.role:roles\/resourcemanager.projectCreator\" --format=\"value(bindings.members)\"\necho; echo \"== 3. Cine poate ata\u0219a proiecte la contul $BILLING_ACCOUNT_ID ==\"\ngcloud billing accounts get-iam-policy \"$BILLING_ACCOUNT_ID\" --flatten=\"bindings[].members\" \\\n  --filter=\"bindings.role:roles\/billing.user\" --format=\"value(bindings.members)\"<\/code><\/pre>\n  <\/div>\n  <div class=\"fotc-cards fotc-steps\">\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag fotc-tag--blue\">pasul 1<\/span><h3>Revizuie\u0219te lista de proiecte<\/h3><p>Fiecare proiect pe care nu \u00eel recuno\u0219ti prime\u0219te un proprietar sau dispare. Repet\u0103 s\u0103pt\u0103m\u00e2nal, ideal din program, cu e-mail.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag fotc-tag--blue\">pasul 2<\/span><h3>Restr\u00e2nge Project Creator \u0219i Billing Account User<\/h3><p>Retrage domeniului rolul implicit, p\u0103streaz\u0103 grupul de administratori. Lista celor care pot ata\u0219a facturarea trebuie s\u0103 \u00eencap\u0103 pe degetele unei m\u00e2ini.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag fotc-tag--blue\">pasul 3<\/span><h3>Cap pe fiecare pereche proiect&nbsp;\u00d7&nbsp;serviciu AI<\/h3><p>Inclusiv acolo unde AI nu este folosit. Acolo o sum\u0103 simbolic\u0103; unde traficul este real, o sum\u0103 derivat\u0103 din istoric \u0219i rotunjit\u0103 \u00een sus.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal><span class=\"fotc-tag fotc-tag--blue\">pasul 4<\/span><h3>Buget pe \u00eentregul cont<\/h3><p>Cu un prag ales dup\u0103 cheltuielile reale, nu dup\u0103 un num\u0103r rotund. Acoper\u0103 \u0219i cheltuielile proiectelor noi ata\u0219ate acestui cont.<\/p><\/div>\n  <\/div>\n  <p>Ace\u0219ti patru pa\u0219i dureaz\u0103 o dup\u0103-amiaz\u0103 \u0219i nu cer nimic \u00een afara consolei. \u00cenchid scenariul din acest text \u00eentr-un mediu mic \u0219i \u00eel fac mult mai greu \u00eentr-unul mare.<\/p>\n  <h2>Rezumat<\/h2>\n  <p>Spend Caps din Google Cloud impun automat suma stabilit\u0103 pentru serviciile acoperite. Pune-le oriunde se poate, cu aten\u021bie special\u0103 la proiectele care nu folosesc AI. Cloud Anomaly Detector adaug\u0103 un al doilea strat de control: compar\u0103 cheltuielile cu istoricul contului, arat\u0103 ce s-a schimbat \u0219i treze\u0219te pe cineva care poate reac\u021biona. O cheltuial\u0103 neobi\u0219nuit\u0103 poate merita aten\u021bie chiar \u0219i atunci c\u00e2nd \u00eencape \u00een limita lunar\u0103.<\/p>\n  <div class=\"fotc-callout\" data-reveal><p>Spend Caps p\u0103zesc suma stabilit\u0103. Cloud Anomaly Detector ajut\u0103 s\u0103 recuno\u0219ti un cost neobi\u0219nuit \u0219i s\u0103 decizi ce faci cu el.<\/p><\/div>\n  <p>Cei patru pa\u0219i de mai sus sunt suficien\u021bi ca s\u0103 \u00eencepi. Pe m\u0103sur\u0103 ce mediul cre\u0219te, verific\u0103 dac\u0103 limitele mai acoper\u0103 ce trebuie. Limita este acolo unde \u00eencepe \u00eentrebarea \u201eeste atac sau o func\u021bie nou\u0103?&#8221;, la zeci de proiecte \u0219i chei \u00eemp\u0103r\u021bite de-a lungul anilor. La FOTC facem asta sub numele Cloud Anomaly Detector: monitorizarea contului de facturare la fiecare jum\u0103tate de or\u0103, cu alerte c\u0103tre echipa ta \u0219i a noastr\u0103, iar c\u00e2nd alerta a venit deja \u0219i nu \u0219tii ce s\u0103 faci cu ea, \u0219i ca suport de inginerie la or\u0103, f\u0103r\u0103 contract lung. Dac\u0103 preferi s\u0103 o faci singur, tot ce este mai sus este suficient ca s\u0103 \u00eencepi.<\/p>\n  <section class=\"fotc-faq\">\n    <h2>\u00centreb\u0103ri frecvente<\/h2>\n    <h3>M\u0103 protejeaz\u0103 spend cap-ul de LLMjacking?<\/h3>\n    <p>Limiteaz\u0103 factura \u00een proiectele pe care le-ai acoperit. Nu opre\u0219te atacul: aplicarea nu este instantanee, cererile \u00een curs se factureaz\u0103, iar un atacator cu drept de a crea proiecte va crea unul nou. Cap-ul, detec\u021bia la nivel de cont \u0219i verificarea rolurilor Project Creator \u0219i Billing Account User \u00eenchid trei bre\u0219e diferite. Niciunul nu \u00eel \u00eenlocuie\u0219te pe celelalte.<\/p>\n    <h3>De ce Cloud Anomaly Detector dac\u0103 am Spend Caps?<\/h3>\n    <p>Spend Caps suspend\u0103 folosirea serviciului indicat dup\u0103 dep\u0103\u0219irea sumei stabilite. Detectorul caut\u0103 abateri de la cheltuielile normale ale contului \u0219i ajut\u0103 s\u0103 indici proiectul \u0219i serviciul responsabile de schimbare. Poate semnala o anomalie care \u00eencape \u00een limit\u0103, dac\u0103 \u00eendepline\u0219te condi\u021biile de alertare. Lucreaz\u0103 din exportul de facturare, a\u0219a c\u0103 timpul de detec\u021bie depinde de momentul \u00een care sosesc datele. Nu exist\u0103 garan\u021bia c\u0103 alerta apare \u00eenainte ca Spend Caps s\u0103 ac\u021bioneze.<\/p>\n    <h3>Ce sum\u0103 setez pe un proiect care nu folose\u0219te AI?<\/h3>\n    <p>Una simbolic\u0103, de ordinul a o sut\u0103 de euro pe lun\u0103. C\u00e2t s\u0103 nu o observe nimeni la un test legitim \u0219i suficient de mic\u0103 \u00eenc\u00e2t s\u0103 opreasc\u0103 abuzul \u00eenainte s\u0103 creasc\u0103. C\u00e2nd proiectul \u00eencepe cu adev\u0103rat s\u0103 foloseasc\u0103 AI, ridici limita \u00een mod con\u0219tient.<\/p>\n    <h3>Poate cap-ul s\u0103-mi opreasc\u0103 produc\u021bia?<\/h3>\n    <p>Da, \u0219i asta e treaba lui. O sum\u0103 aleas\u0103 gre\u0219it pe un proiect cu trafic real va opri inferen\u021ba legitim\u0103, iar limita se ridic\u0103 singur\u0103 abia la \u00eenceputul lunii urm\u0103toare. P\u00e2n\u0103 atunci trebuie ridicat\u0103 de un om, iar revenirea dureaz\u0103 p\u00e2n\u0103 la o or\u0103. De aceea pe proiectele de produc\u021bie suma vine din istoric \u0219i se rotunje\u0219te \u00een sus, iar cineva cu acces la buget trebuie s\u0103 fie disponibil \u0219i \u00een weekend.<\/p>\n    <h3>Ce se \u00eent\u00e2mpl\u0103 dac\u0103 exportul de facturare c\u0103tre BigQuery se opre\u0219te?<\/h3>\n    <p>Este singura stare pe care un detector de anomalii de cost nu o observ\u0103 singur, pentru c\u0103 lipsa datelor arat\u0103 ca lipsa cheltuielilor. Prospe\u021bimea sursei trebuie s\u0103 fie o metric\u0103 separat\u0103, cu alarm\u0103 separat\u0103, iar dashboard-ul trebuie s\u0103 spun\u0103 \u201enu v\u0103d&#8221; \u00eentr-o alt\u0103 culoare dec\u00e2t \u201enu se \u00eent\u00e2mpl\u0103 nimic&#8221;.<\/p>\n  <\/section>\n  <p class=\"fotc-note\">Descrierea spend cap-urilor provine din <a href=\"https:\/\/docs.cloud.google.com\/billing\/docs\/how-to\/budgets-spend-caps\">documenta\u021bia public\u0103 Google Cloud<\/a>, verificat\u0103 la 23 octombrie 2026. Din 13 octombrie 2026 Spend Caps din Cloud Billing sunt disponibile general (GA), iar documenta\u021bia anun\u021b\u0103 o list\u0103 extins\u0103 de servicii. Cifrele despre activitatea detectorului sunt reale \u0219i provin din jurnalele lui. Harta acoperirii \u0219i graficul baseline-ului ilustreaz\u0103 mecanismul \u0219i nu sunt capturi din produc\u021bie. Articolul descrie sistemul \u00een versiunea 6.x (2026).<\/p>\n  <div class=\"fotc-tip\" id=\"fotcTip\" role=\"presentation\"><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Dac\u0103 r\u0103spunzi de un cont de facturare Google Cloud \u00een care cineva de\u021bine o cheie API pentru un serviciu AI, acest text este pentru tine. Google a ad\u0103ugat recent \u00een Cloud Billing limitele de cheltuieli (spend caps), un mecanism care suspend\u0103 automat serviciul acoperit dup\u0103 dep\u0103\u0219irea unei sume stabilite. Combinat cu detectorul de anomalii de&#8230;<\/p>\n","protected":false},"author":22,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","footnotes":""},"categories":[562],"tags":[],"class_list":["post-178555","post","type-post","status-publish","format-standard","hentry","category-google-cloud-platform-ro"],"_links":{"self":[{"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/posts\/178555","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/comments?post=178555"}],"version-history":[{"count":0,"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/posts\/178555\/revisions"}],"wp:attachment":[{"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/media?parent=178555"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/categories?post=178555"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fotc.com\/ro\/wp-json\/wp\/v2\/tags?post=178555"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}