{"id":176975,"date":"2026-09-30T14:23:18","date_gmt":"2026-09-30T12:23:18","guid":{"rendered":"https:\/\/fotc.com\/?p=176975"},"modified":"2026-11-04T10:21:00","modified_gmt":"2026-11-04T09:21:00","slug":"limit-wydatkow-w-google-cloud-chroni-projekt-detektor-anomalii-pomaga-ocenic-co-dzieje-sie-na-calym-koncie","status":"publish","type":"post","link":"https:\/\/fotc.com\/pl\/blog\/limit-wydatkow-w-google-cloud-chroni-projekt-detektor-anomalii-pomaga-ocenic-co-dzieje-sie-na-calym-koncie\/","title":{"rendered":"Limit wydatk\u00f3w w\u00a0Google Cloud chroni projekt. Detektor anomalii pomaga oceni\u0107, co dzieje si\u0119 na ca\u0142ym koncie"},"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\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\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\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\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\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\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\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\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\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\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\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\": \"dzie\u0144\", \"per_run\": \"kont na przebieg\", \"alert1\": \"alert\", \"alertN\": \"alert\u00f3w\", \"legend\": \"alerty wys\u0142ane do ludzi\", \"obs\": \"obserwacji\", \"project\": \"projekt\", \"service\": \"us\u0142uga\", \"api\": \"limit potwierdzony przez API\", \"man\": \"limit zadeklarowany przez cz\u0142owieka\", \"none\": \"bez limitu\", \"mean\": \"\u015brednia\", \"median\": \"mediana\", \"incident\": \"incydent\", \"day1\": \"dzie\u0144 1\", \"day21\": \"dzie\u0144 21 \u00b7 o\u015b pierwiastkowa\", \"days\": \" dni\", \"copy\": \"Kopiuj\", \"copied\": \"Skopiowano\", \"expand\": \"Rozwi\u0144\", \"collapse\": \"Zwi\u0144\", \"zout\": \"Drugi dzie\u0144 incydentu: z ze \u015bredni\u0105 i odchyleniem <b style='color:#E3322B'>{a}<\/b>, z z median\u0105 i MAD <b style='color:#00A855'>{b}<\/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) { if (!tip) return; 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 () { if (tip) tip.style.opacity = 0; };\n    var fmt = function (n) { return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, \" \"); };\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 (k) { if (onIn[k]) { onIn[k](); delete onIn[k]; } }\n        io.unobserve(en.target);\n      });\n    }, { threshold: 0.05 }) : 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 (k) { if (onIn[k]) onIn[k](); } } });\n    setTimeout(function () {\n      root.querySelectorAll(\"[data-reveal]:not(.is-in)\").forEach(function (n) {\n        var r = n.getBoundingClientRect();\n        if (r.top < window.innerHeight) n.classList.add(\"is-in\");\n      });\n    }, 1500);\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    \/* ---- 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    \/* ---- 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    \/* ---- 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 = p < 0.18 + 0.3 * w ? \"api\" : 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.innerHTML = \"<i style='width:\" + (100 * n.api \/ tot) + \"%;background:#00A855'><\/i><i style='width:\" + (100 * n.man \/ tot) + \"%;background:#FAB001'><\/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    \/* ---- 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; 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    \/* ---- 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 = s.length >> 1; 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(w.length >= 3 ? w.reduce(function (a, b) { return a + b; }, 0) \/ w.length : null); med.push(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.innerHTML = LX.zout.replace(\"{a}\", zMean.toFixed(1)).replace(\"{b}\", zMed.toFixed(0));\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    \/* ---- 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; k <= n; 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    \/* ---- 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\">Limity wydatk\u00f3w (spend caps) w&nbsp;Google Cloud pozwalaj\u0105 automatycznie wstrzyma\u0107 u\u017cycie obj\u0119tej nimi us\u0142ugi po przekroczeniu ustalonej kwoty. Cloud Anomaly Detector pomaga odpowiedzie\u0107 na inne pytanie \u2013 czy wydatki s\u0105 normalne dla tego konta i&nbsp;co spowodowa\u0142o zmian\u0119. Limit pilnuje kwoty. Detektor m\u00f3wi, gdzie szuka\u0107 przyczyny.<\/p>\n  <p class=\"fotc-lead\">Je\u015bli odpowiadasz za konto rozliczeniowe Google Cloud, na kt\u00f3rym ktokolwiek ma klucz API do us\u0142ugi AI, ten tekst jest dla Ciebie. Dowiesz si\u0119, co taki limit daje i&nbsp;gdzie si\u0119 ko\u0144czy, jak roz\u0142o\u017cy\u0107 limity na kilkadziesi\u0105t projekt\u00f3w bez zatrzymania produkcji, i&nbsp;dostaniesz trzy komendy, kt\u00f3rymi sprawdzisz swoje konto jeszcze dzi\u015b.<\/p>\n\n  <div class=\"fotc-stats\" role=\"group\" aria-label=\"Miesi\u0105c pracy detektora w liczbach\">\n    <div class=\"fotc-stat\" data-reveal=\"\"><b data-count=\"48\">48<\/b><span>przebieg\u00f3w detektora na dob\u0119, na setkach kont naraz<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal=\"\"><b data-count=\"20\" data-prefix=\"\u2248 \">\u2248 20<\/b><span>kont odstaj\u0105cych statystycznie w&nbsp;typowym przebiegu<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal=\"\"><b data-count=\"16\">16<\/b><span>alert\u00f3w do ludzi w&nbsp;29 dni. Reszta to obserwacje.<\/span><\/div>\n    <div class=\"fotc-stat\" data-reveal=\"\"><b data-count=\"2\">2<\/b><span>alerty wymaga\u0142y wsp\u00f3lnej z&nbsp;klientem lokalizacji \u017ar\u00f3d\u0142a<\/span><\/div>\n  <\/div>\n\n  <p>W&nbsp;<a href=\"https:\/\/fotc.com\/pl\/blog\/kiedy-przychodzi-rachunek-jest-juz-za-pozno-jak-zbudowalismy-detektor-anomalii-kosztowych-w-google-cloud\/\">pierwszej cz\u0119\u015bci<\/a> opisali\u015bmy dwa incydenty LLMjackingu i&nbsp;detektor, kt\u00f3ry drugi z&nbsp;nich zatrzyma\u0142 w&nbsp;kilka godzin. Od tego czasu system przepracowa\u0142 pe\u0142ny miesi\u0105c na setkach kont billingowych, zmieni\u0142 nazw\u0119 na Cloud Anomaly Detector i&nbsp;dosta\u0142 drug\u0105 zak\u0142adk\u0119, po\u015bwi\u0119con\u0105 limitom wydatk\u00f3w. Zaczynamy od nich, bo to one zmieniaj\u0105 uk\u0142ad si\u0142.<\/p>\n\n  <h2>Limit wydatk\u00f3w \u2013 jak dzia\u0142a Spend Caps<\/h2>\n  <p>Bud\u017cety w&nbsp;Cloud Billing istnia\u0142y od dawna, ale tylko powiadamia\u0142y. Limit wydatk\u00f3w, po angielsku spend cap, to bud\u017cet z&nbsp;w\u0142\u0105czon\u0105 egzekucj\u0105: gdy szacowany koszt wskazanej us\u0142ugi we wskazanym projekcie przekroczy kwot\u0119, Google zatrzymuje nowe \u017c\u0105dania do tej us\u0142ugi. Zakres to jeden projekt i&nbsp;jedna us\u0142uga: Gemini API, Gemini Enterprise Agent Platform (dawniej Vertex AI), Cloud Run albo Cloud Run functions. Okres wy\u0142\u0105cznie miesi\u0119czny, status Preview. Stan dokumentacji na 14 wrze\u015bnia 2026. Google zapowiada obj\u0119cie kolejnych us\u0142ug w&nbsp;nast\u0119pnych wydaniach. Im wi\u0119cej us\u0142ug obejm\u0105 Spend Caps, tym wi\u0119cej da si\u0119 nimi zatrzyma\u0107. Detektor anomalii odpowiada na inne pytanie: czy ten wydatek jest normalny i&nbsp;sk\u0105d si\u0119 wzi\u0105\u0142.<\/p>\n  <figure data-reveal=\"\" data-trigger=\"timeline\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Jeden miesi\u0105c \u017cycia limitu wydatk\u00f3w<\/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\">Miesi\u0105c limitu wydatk\u00f3w<\/title>\n<desc id=\"tlD\">O\u015b od pierwszego dnia miesi\u0105ca. E-mail przy 50, 80 i 100 procent kwoty; po 100 procent Google zatrzymuje nowe wywo\u0142ania us\u0142ugi w projekcie. Zdj\u0119cie limitu tylko r\u0119cznie.<\/desc>\n<line x1=\"30\" x2=\"690\" y1=\"78\" y2=\"78\" stroke=\"#E8E8E8\" stroke-width=\"8\" stroke-linecap=\"round\"><\/line>\n<line class=\"tl-fill\" x1=\"30\" x2=\"505.2\" y1=\"78\" y2=\"78\" stroke=\"#0097DA\" stroke-width=\"8\" stroke-linecap=\"round\" pathLength=\"100\"><\/line>\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\"><\/line>\n<text x=\"30\" y=\"114\" font-size=\"11\" fill=\"#6B6B6B\">1. dzie\u0144 miesi\u0105ca<\/text>\n<text x=\"690\" y=\"114\" text-anchor=\"end\" font-size=\"11\" fill=\"#6B6B6B\">koniec miesi\u0105ca<\/text>\n<text class=\"tl-stoplabel\" x=\"597.6\" y=\"56\" text-anchor=\"middle\" font-size=\"12\" font-weight=\"500\" fill=\"#E3322B\" opacity=\"0\">blokada wywo\u0142a\u0144<\/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\"><\/circle>\n<circle class=\"tl-dot\" cx=\"267.6\" cy=\"78\" r=\"9\" fill=\"#fff\" stroke=\"#FAB001\" stroke-width=\"3\"><\/circle>\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\"><\/circle>\n<circle class=\"tl-dot\" cx=\"412.8\" cy=\"78\" r=\"9\" fill=\"#fff\" stroke=\"#FAB001\" stroke-width=\"3\"><\/circle>\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\"><\/circle>\n<circle class=\"tl-dot\" cx=\"505.2\" cy=\"78\" r=\"9\" fill=\"#fff\" stroke=\"#E3322B\" stroke-width=\"3\"><\/circle>\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 + blokada<\/text><\/g>\n<circle class=\"tl-cursor\" cx=\"30\" cy=\"78\" r=\"6\" fill=\"#0097DA\" stroke=\"#fff\" stroke-width=\"2\"><\/circle>\n<\/svg>\n    <figcaption>Progi 50 i&nbsp;80&nbsp;% to wy\u0142\u0105cznie e-mail do administrator\u00f3w billingu i&nbsp;w\u0142a\u015bcicieli projektu. Po 100&nbsp;% Google blokuje nowe wywo\u0142ania, ale \u017c\u0105dania w&nbsp;locie doko\u0144cz\u0105 si\u0119 i&nbsp;trafi\u0105 na faktur\u0119. Zdj\u0119cie limitu jest r\u0119czne i&nbsp;trwa do godziny.<\/figcaption>\n  <\/figure>\n  <div class=\"fotc-cols\">\n    <div class=\"fotc-col fotc-col--stop\" data-reveal=\"\"><h3>Co blokuje limit<\/h3><ul><li>Nowe wywo\u0142ania API wskazanej us\u0142ugi w&nbsp;tym projekcie<\/li><li>Do czasu r\u0119cznej edycji bud\u017cetu<\/li><\/ul><\/div>\n    <div class=\"fotc-col fotc-col--flow\" data-reveal=\"\"><h3>Co dzia\u0142a dalej<\/h3><ul><li>Compute, storage i&nbsp;inne zasoby trwa\u0142e<\/li><li>Inne projekty i&nbsp;inne us\u0142ugi<\/li><li>Koszt \u017c\u0105da\u0144 w&nbsp;locie, egzekucja nie jest natychmiastowa<\/li><\/ul><\/div>\n  <\/div>\n  <p>Ka\u017cdemu, kto ma w&nbsp;organizacji cho\u0107 jeden klucz API do us\u0142ugi AI, powtarzamy to samo: <strong>tam, gdzie Spend Caps s\u0105 dost\u0119pne, za\u0142\u00f3\u017c limit na ka\u017cdym projekcie, w&nbsp;kt\u00f3rym ta us\u0142uga jest w\u0142\u0105czona, i&nbsp;na ka\u017cdym, w&nbsp;kt\u00f3rym nie jest<\/strong>. Ten drugi przypadek jest wa\u017cniejszy. Majowy incydent z&nbsp;cz\u0119\u015bci pierwszej to atakuj\u0105cy w\u0142\u0105czaj\u0105cy us\u0142ug\u0119 AI w&nbsp;projekcie, kt\u00f3ry nigdy jej nie u\u017cywa\u0142. Limit na sto euro w&nbsp;projekcie, kt\u00f3ry nie ma prawa wyda\u0107 ani centa na AI, pomaga ograniczy\u0107 koszt takiego nadu\u017cycia. Kwot\u0119 trzeba dobra\u0107 \u015bwiadomie, uwzgl\u0119dniaj\u0105c op\u00f3\u017anienie egzekucji.<\/p>\n\n  <h2>Limit na projekcie i&nbsp;monitoring ca\u0142ego konta<\/h2>\n  <p>Limit wydatk\u00f3w chroni wskazan\u0105 par\u0119 projekt&nbsp;\u00d7&nbsp;us\u0142uga. Monitoring ca\u0142ego konta obejmuje r\u00f3wnie\u017c koszty nowo pod\u0142\u0105czanych projekt\u00f3w. Domy\u015blna konfiguracja Google Cloud nadaje rol\u0119 Project Creator wszystkim u\u017cytkownikom domeny, a&nbsp;Billing Account User bywa rozdany szerzej, ni\u017c ktokolwiek pami\u0119ta. Przy takich uprawnieniach skrypt mo\u017ce za\u0142o\u017cy\u0107 nowy projekt, podpi\u0105\u0107 billing i&nbsp;uruchomi\u0107 us\u0142ug\u0119 AI. Wszystkie Twoje limity i&nbsp;bud\u017cety projektowe stoj\u0105 w&nbsp;starych projektach.<\/p>\n  <figure data-reveal=\"\" data-trigger=\"attack\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">\u015acie\u017cka przez nowy projekt i&nbsp;trzy warstwy kontroli<\/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\">\u015acie\u017cka przez nowy projekt i trzy warstwy kontroli<\/title>\n<desc id=\"atD\">Atakuj\u0105cy zak\u0142ada nowy projekt, podpina billing i w\u0142\u0105cza us\u0142ug\u0119 AI. Spend cap i bud\u017cet projektowy obejmuj\u0105 wskazane projekty. Detekcja na koncie rozliczeniowym obejmuje nowe projekty po pojawieniu si\u0119 danych.<\/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\"><\/path><\/marker><\/defs>\n<g class=\"at-step at-step-0\"><rect x=\"20\" y=\"20\" width=\"250\" height=\"56\" rx=\"12\"><\/rect><text class=\"t\" x=\"34\" y=\"46\" font-size=\"13\" font-weight=\"500\">Wyciek\u0142e po\u015bwiadczenia<\/text><text class=\"s\" x=\"34\" y=\"66\" font-size=\"11.5\">rola z prawem tworzenia projekt\u00f3w<\/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)\"><\/line>\n<g class=\"at-step at-step-1\"><rect x=\"20\" y=\"92\" width=\"250\" height=\"56\" rx=\"12\"><\/rect><text class=\"t\" x=\"34\" y=\"118\" font-size=\"13\" font-weight=\"500\">Nowy projekt<\/text><text class=\"s\" x=\"34\" y=\"138\" font-size=\"11.5\">Project Creator, cz\u0119sto domy\u015blnie<\/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)\"><\/line>\n<g class=\"at-step at-step-2\"><rect x=\"20\" y=\"164\" width=\"250\" height=\"56\" rx=\"12\"><\/rect><text class=\"t\" x=\"34\" y=\"190\" font-size=\"13\" font-weight=\"500\">Podpi\u0119cie billingu<\/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)\"><\/line>\n<g class=\"at-step at-step-3\"><rect x=\"20\" y=\"236\" width=\"250\" height=\"56\" rx=\"12\"><\/rect><text class=\"t\" x=\"34\" y=\"262\" font-size=\"13\" font-weight=\"500\">API us\u0142ugi AI + klucz<\/text><text class=\"s\" x=\"34\" y=\"282\" font-size=\"11.5\">konsumpcja od pierwszej minuty<\/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)\"><\/path>\n<g class=\"at-layer at-layer-0 at-miss\"><rect x=\"430\" y=\"48\" width=\"270\" height=\"60\" rx=\"12\"><\/rect><text class=\"t\" x=\"444\" y=\"74\" font-size=\"13\" font-weight=\"500\">Limit wydatk\u00f3w<\/text><text class=\"s\" x=\"444\" y=\"94\" font-size=\"11.5\">wskazany projekt i us\u0142uga<\/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\">egzekwuje kwot\u0119<\/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)\"><\/path>\n<g class=\"at-layer at-layer-1 at-miss\"><rect x=\"430\" y=\"138\" width=\"270\" height=\"60\" rx=\"12\"><\/rect><text class=\"t\" x=\"444\" y=\"164\" font-size=\"13\" font-weight=\"500\">Bud\u017cet projektowy<\/text><text class=\"s\" x=\"444\" y=\"184\" font-size=\"11.5\">wskazany projekt<\/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\">alertuje o kwocie<\/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)\"><\/path>\n<g class=\"at-layer at-layer-2 at-hit\"><rect x=\"430\" y=\"228\" width=\"270\" height=\"60\" rx=\"12\"><\/rect><text class=\"t\" x=\"444\" y=\"254\" font-size=\"13\" font-weight=\"500\">Detekcja na koncie<\/text><text class=\"s\" x=\"444\" y=\"274\" font-size=\"11.5\">tak\u017ce nowe projekty<\/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\">ocenia i eskaluje<\/text>\n<circle class=\"at-runner\" cx=\"145\" cy=\"20\" r=\"6\" fill=\"#E3322B\" opacity=\"0\"><\/circle>\n<\/svg>\n    <figcaption>Limit i&nbsp;bud\u017cet projektowy obejmuj\u0105 wskazany projekt. Monitoring konta rozliczeniowego obejmuje tak\u017ce koszty nowych projekt\u00f3w, gdy pojawi\u0105 si\u0119 w&nbsp;danych rozliczeniowych.<\/figcaption>\n  <\/figure>\n  <p><strong>W&nbsp;jednym z&nbsp;w\u0142ama\u0144, przy kt\u00f3rych pomagali\u015bmy, atakuj\u0105cy utworzy\u0142 nowy projekt i&nbsp;podpi\u0105\u0142 go pod konto rozliczeniowe ofiary.<\/strong> Klient zobaczy\u0142 rachunek, kt\u00f3rego nie umia\u0142 sobie wyt\u0142umaczy\u0107, i&nbsp;poprosi\u0142 nas o&nbsp;pomoc. Pierwsze pytanie nie brzmia\u0142o \u201ekto\", tylko \u201ekt\u00f3ry projekt\". Dop\u00f3ki koszt nie ma adresu, w&nbsp;logach nie ma czego szuka\u0107.<\/p>\n  <p>Gdy projekt by\u0142 ju\u017c znany, reszt\u0119 dopowiedzia\u0142y logi audytowe. Ten projekt nie nale\u017ca\u0142 do \u017cadnego \u015brodowiska, kt\u00f3re klient rozpoznawa\u0142, bo kto\u015b za\u0142o\u017cy\u0142 go od zera i&nbsp;podpi\u0105\u0142 pod konto rozliczeniowe. Ten przypadek pokazuje, dlaczego warto obserwowa\u0107 zmiany na ca\u0142ym koncie rozliczeniowym. Nowy projekt pojawi\u0142 si\u0119 na nim jako nowa pozycja kosztowa. Nowa encja, kt\u00f3ra od pierwszej godziny wydaje, jest dla detektora osobnym typem anomalii.<\/p>\n  <p>St\u0105d zasada dla ca\u0142ego portfela: <strong>limit wydatk\u00f3w egzekwuje ustalon\u0105 kwot\u0119, a&nbsp;detekcja na poziomie konta rozliczeniowego rozpoznaje nietypowe zmiany, tak\u017ce w&nbsp;nowych projektach<\/strong>. Do tego dwie rzeczy po stronie IAM, kt\u00f3re kosztuj\u0105 niewiele: odebranie domy\u015blnej roli Project Creator ca\u0142ej domenie i&nbsp;przegl\u0105d, kto naprawd\u0119 ma Billing Account User.<\/p>\n\n  <h3>Kiedy wystarcz\u0105 limity i&nbsp;r\u0119czny przegl\u0105d<\/h3>\n  <p>Kilka projekt\u00f3w, jeden zesp\u00f3\u0142, Project Creator odebrany domenie: wtedy limity na obs\u0142ugiwanych parach projekt&nbsp;\u00d7&nbsp;us\u0142uga AI, bud\u017cet na ca\u0142e konto i&nbsp;regularny przegl\u0105d wydatk\u00f3w mog\u0105 wystarczy\u0107 do codziennej kontroli koszt\u00f3w. Ta rada przestaje dzia\u0142a\u0107, gdy projekt\u00f3w jest kilkadziesi\u0105t, a&nbsp;lista os\u00f3b z&nbsp;prawem do tworzenia projekt\u00f3w jest d\u0142u\u017csza ni\u017c lista ludzi, kt\u00f3rzy o&nbsp;tym wiedz\u0105.<\/p>\n\n  <h2>Jak podeszli\u015bmy do limit\u00f3w w&nbsp;skali<\/h2>\n  <p>Przy setkach kont i&nbsp;tysi\u0105cach projekt\u00f3w potrzebna jest mapa. Zbudowali\u015bmy j\u0105 jako drug\u0105 zak\u0142adk\u0119 dashboardu detektora. Kilka decyzji z&nbsp;tej pracy przenosi si\u0119 na ka\u017cd\u0105 skal\u0119.<\/p>\n  <figure data-reveal=\"\" data-trigger=\"coverage\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Mapa pokrycia: para projekt&nbsp;\u00d7&nbsp;us\u0142uga 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\">deklaracja<\/button>\n        <button type=\"button\" class=\"fotc-btn fotc-btn--dot\" style=\"--dot:#D1D1D1\" data-filter=\"none\">bez limitu<\/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> potwierdzone przez API<\/span><span><b data-n=\"man\">0<\/b> zadeklarowane przez cz\u0142owieka<\/span><span><b data-n=\"none\">0<\/b> bez limitu<\/span><\/div>\n    <figcaption>Ka\u017cde pole to para projekt&nbsp;\u00d7&nbsp;us\u0142uga AI. Mianownikiem jest ka\u017cdy projekt podpi\u0119ty do konta, tak\u017ce bez historii u\u017cycia AI. Zielone i&nbsp;\u017c\u00f3\u0142te nigdy nie sumuj\u0105 si\u0119 w&nbsp;jeden procent. Uk\u0142ad pogl\u0105dowy, nie zrzut z&nbsp;produkcji; animacja pokazuje kolejno\u015b\u0107 pracy od najmniejszych \u015brodowisk.<\/figcaption>\n  <\/figure>\n  <p>Pierwsza dotyczy mianownika, czyli tego, co w&nbsp;og\u00f3le wchodzi do rachunku pokrycia. Liczymy ka\u017cdy projekt podpi\u0119ty do konta, tak\u017ce taki, kt\u00f3ry nigdy nie dotkn\u0105\u0142 AI. Gdyby\u015bmy liczyli wy\u0142\u0105cznie projekty ju\u017c u\u017cywaj\u0105ce AI, mapa \u015bwieci\u0142aby si\u0119 na zielono i&nbsp;nie m\u00f3wi\u0142a nic o&nbsp;ryzyku. Zabezpieczasz to, co atakuj\u0105cy dopiero w\u0142\u0105czy.<\/p>\n  <p>Druga: bud\u017cet bez filtra us\u0142ugi nie jest limitem, tylko alertem, i&nbsp;nie wolno go wlicza\u0107 do pokrycia. Gdy policzyli\u015bmy to naiwnie, pokrycie wysz\u0142o kilkadziesi\u0105t razy lepsze, ni\u017c by\u0142o naprawd\u0119.<\/p>\n  <p>Trzecia dotyczy kwoty. Proponuje j\u0105 historia konta: wi\u0119ksza z&nbsp;dw\u00f3ch warto\u015bci, \u015bredniej z&nbsp;pe\u0142nych miesi\u0119cy i&nbsp;ostatniego pe\u0142nego miesi\u0105ca. Bie\u017c\u0105cy miesi\u0105c jest wykluczony, bo limit policzony w&nbsp;po\u0142owie miesi\u0105ca jest policzony z&nbsp;po\u0142owy ruchu. Zaokr\u0105glamy zawsze w&nbsp;g\u00f3r\u0119 \u2013 limit zaokr\u0105glony w&nbsp;d\u00f3\u0142 stoi poni\u017cej ruchu, pod kt\u00f3ry by\u0142 liczony. Projekt bez wydatku dostaje kwot\u0119 symboliczn\u0105.<\/p>\n  <p>Czwarta jest o&nbsp;kolejno\u015bci. List\u0119 robimy od najmniejszych \u015brodowisk, bo tam pomy\u0142ka kosztuje najmniej, a&nbsp;projekt\u00f3w bez historii jest najwi\u0119cej. Du\u017ce \u015brodowiska z&nbsp;realnym ruchem na AI dostaj\u0105 limit dopiero po rozmowie o&nbsp;kwocie. \u0179le dobrany limit zatrzyma produkcj\u0119 klienta r\u00f3wnie skutecznie jak atak.<\/p>\n  <p>I&nbsp;regu\u0142a, kt\u00f3rej nie wolno z\u0142ama\u0107: to, co potwierdzi\u0142o API, i&nbsp;to, co cz\u0142owiek oznaczy\u0142 r\u0119cznie w&nbsp;konsoli, maj\u0105 na mapie osobne kolory i&nbsp;nigdy nie sumuj\u0105 si\u0119 w&nbsp;jeden procent. Deklaracja jest cenna, bo niesie nazwisko i&nbsp;dat\u0119. Dowodem jest tylko odczyt.<\/p>\n\n  <h2>Miesi\u0105c produkcji: mi\u0119dzy \u201eodstaje\" a&nbsp;\u201ewarto obudzi\u0107 cz\u0142owieka\"<\/h2>\n  <p>Detektor por\u00f3wnuje ka\u017cde konto wy\u0142\u0105cznie z&nbsp;jego w\u0142asn\u0105 histori\u0105, wi\u0119c w&nbsp;ka\u017cdym przebiegu kilkana\u015bcie kont odstaje od normy, i&nbsp;tak ma by\u0107. Ca\u0142a warto\u015b\u0107 systemu siedzi w&nbsp;tym, jak z&nbsp;tych obserwacji wybiera kilkana\u015bcie w&nbsp;miesi\u0105cu, przy kt\u00f3rych ma prawo komu\u015b przerwa\u0107 dzie\u0144.<\/p>\n  <figure data-reveal=\"\" data-trigger=\"cadence\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\" data-cad-counter=\"\">obserwacje kontra alerty<\/span><div class=\"fotc-controls\"><button type=\"button\" class=\"fotc-btn\" data-only-alerts=\"\">Tylko dni z&nbsp;alertami<\/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\">Konta odstaj\u0105ce na przebieg i alerty wys\u0142ane, 29 dni<\/title>\n      <desc id=\"cadD\">S\u0142upki od 4 do 46 kont na przebieg; alerty tylko w dziesi\u0119ciu dniach, \u0142\u0105cznie szesna\u015bcie.<\/desc>\n    <\/svg>\n    <figcaption>\u015arednia liczba kont odstaj\u0105cych na jeden przebieg (s\u0142upki) i&nbsp;alerty wys\u0142ane do ludzi (czerwone znaczniki). 29 kolejnych dni, sierpie\u0144 i&nbsp;wrzesie\u0144 2026, dane z&nbsp;log\u00f3w detektora. Dni z&nbsp;najwi\u0119ksz\u0105 liczb\u0105 obserwacji nie by\u0142y dniami z&nbsp;alertami.<\/figcaption>\n  <\/figure>\n\n  <p>Konkret z&nbsp;3 wrze\u015bnia. Konto, kt\u00f3re zwykle wydaje kilkana\u015bcie euro dziennie, w&nbsp;jednej dobie wyda\u0142o 500. Mno\u017cnik \u00d723, wzrost o&nbsp;2200 procent, praktycznie ca\u0142o\u015b\u0107 na jednej us\u0142udze map. Detektor zobaczy\u0142 to o&nbsp;3:00 w&nbsp;nocy i&nbsp;przez kolejne 21 godzin potwierdzi\u0142 42 razy. Nie obudzi\u0142 nikogo, bo 500 euro nie przebija progu kwotowego, przy kt\u00f3rym wolno komu\u015b przerwa\u0107 dzie\u0144.<\/p>\n  <p>Alert wyszed\u0142 nast\u0119pnego dnia rano, ale z&nbsp;innego detektora. Prognoza na koniec miesi\u0105ca si\u0119gn\u0119\u0142a 6000 euro wobec 900 w&nbsp;poprzednich miesi\u0105cach. Dzie\u0144 by\u0142 za ma\u0142y, miesi\u0105c ju\u017c nie.<\/p>\n  <p>To nie by\u0142 atak. U&nbsp;klienta przesta\u0142a dzia\u0142a\u0107 cz\u0119\u015b\u0107 mapowa, ruch przepi\u0105\u0142 si\u0119 na zapasowy projekt i&nbsp;zacz\u0105\u0142 generowa\u0107 koszt tam, gdzie nikt go nie pilnowa\u0142. Awaria, nie w\u0142amanie, a&nbsp;rachunek r\u00f3s\u0142by tak samo d\u0142ugo, jak d\u0142ugo nikt by nie zajrza\u0142.<\/p>\n\n  <h3>Alert ma wskaza\u0107 palcem<\/h3>\n  <p>Dwa z&nbsp;szesnastu alert\u00f3w sko\u0144czy\u0142y si\u0119 wsp\u00f3ln\u0105 sesj\u0105 z&nbsp;klientem. W&nbsp;obu przypadkach wygl\u0105da\u0142o to tak samo: karta m\u00f3wi\u0142a, \u017ce konto wydaje wielokrotnie wi\u0119cej ni\u017c zwykle, a&nbsp;po drugiej stronie nikt nie potrafi\u0142 wskaza\u0107, z&nbsp;kt\u00f3rego projektu to leci ani kt\u00f3ry klucz za tym stoi. Konto rozliczeniowe to za gruby adres, \u017ceby cokolwiek z&nbsp;nim zrobi\u0107.<\/p>\n  <p>Detekcja zesz\u0142a wi\u0119c ni\u017cej, na pojedynczy projekt i&nbsp;pojedynczy SKU, czyli najdrobniejsz\u0105 pozycj\u0119, jak\u0105 Google wypisuje na fakturze. Us\u0142ugi na karcie sortujemy po tym, o&nbsp;ile realnie uros\u0142y w&nbsp;pieni\u0105dzach, nie po procentach. Us\u0142uga, kt\u00f3ra skoczy\u0142a z&nbsp;centa na siedem euro, uros\u0142a o&nbsp;siedemset procent i&nbsp;jest bez znaczenia. Us\u0142uga, kt\u00f3ra uros\u0142a o&nbsp;siedem tysi\u0119cy euro, jest ca\u0142ym zdarzeniem.<\/p>\n  <p>Rozbicie liczy si\u0119 w&nbsp;tym samym przebiegu co reszta karty i&nbsp;to nie jest szczeg\u00f3\u0142 implementacyjny. Eksport billingowy dope\u0142nia okno jeszcze wiele godzin po alercie, wi\u0119c us\u0142ugi policzone dopiero przy otwieraniu karty pochodzi\u0142yby z&nbsp;p\u00f3\u017aniejszego stanu danych ni\u017c kwota w&nbsp;nag\u0142\u00f3wku. Karta ma by\u0107 zdj\u0119ciem jednej chwili, nawet je\u015bli ta chwila jest niekompletna.<\/p>\n\n  <h3>Baseline odporny na to, co mierzy<\/h3>\n  <p>Najpierw bez matematyki. Detektor odpowiada codziennie na jedno pytanie: czy dzisiejszy koszt jest normalny dla tego konta? Potrzebuje do tego dw\u00f3ch liczb: co jest norm\u0105 i&nbsp;o&nbsp;ile dzie\u0144 zwykle od niej odbiega. K\u0142opot zaczyna si\u0119 wtedy, gdy w&nbsp;ostatnim tygodniu zdarzy\u0142 si\u0119 ju\u017c jeden z\u0142y dzie\u0144.<\/p>\n  <p>Przyk\u0142ad. Konto wydaje oko\u0142o 20 euro dziennie. W&nbsp;poniedzia\u0142ek kto\u015b uruchamia inferencj\u0119 za 900, we wtorek za 640. Poniedzia\u0142ek z\u0142apie ka\u017cda metoda, bo 900 wobec 20 to inny \u015bwiat. Ciekawy jest wtorek.<\/p>\n  <p><strong>Ze \u015bredni\u0105.<\/strong> \u015arednia z&nbsp;ostatnich siedmiu dni, w&nbsp;tym z&nbsp;poniedzia\u0142ku za 900, wynosi 146. Zwyk\u0142y rozrzut, liczony z&nbsp;tych samych dni, ur\u00f3s\u0142 z&nbsp;2 do ponad 300, bo poniedzia\u0142ek rozci\u0105gn\u0105\u0142 go w&nbsp;g\u00f3r\u0119. Wtorek za 640 jest od tej nowej normy oddalony o&nbsp;nieca\u0142e dwa takie rozrzuty. Dla statystyki to zwyk\u0142y dzie\u0144. Alarm ga\u015bnie w&nbsp;\u015brodku incydentu.<\/p>\n  <p><strong>Z&nbsp;median\u0105.<\/strong> Mediana to \u015brodkowa warto\u015b\u0107 po posortowaniu. Siedem dni to 17, 19, 20, 21, 22, 23 i&nbsp;900, \u015brodkowa to 21. Poniedzia\u0142ek stoi na ko\u0144cu listy i&nbsp;nie ma nic do powiedzenia. Rozrzut liczymy t\u0105 sam\u0105 sztuczk\u0105: bierzemy odleg\u0142o\u015b\u0107 ka\u017cdego dnia od 21 i&nbsp;znowu \u015brodkow\u0105 z&nbsp;nich, wychodzi 2. To jest MAD, mediana odchyle\u0144. Wtorek za 640 jest oddalony od normy o&nbsp;kilkaset takich rozrzut\u00f3w. Alarm trwa, bo powinien.<\/p>\n  <div class=\"fotc-table-scroll\">\n  <table>\n    <thead><tr><th><\/th><th>\u015arednia<\/th><th>Mediana<\/th><\/tr><\/thead>\n    <tbody>\n      <tr><td>Norma po poniedzia\u0142ku<\/td><td>146<\/td><td>21<\/td><\/tr>\n      <tr><td>Zwyk\u0142y rozrzut<\/td><td>ponad 300<\/td><td>2<\/td><\/tr>\n      <tr><td>Wtorek za 640<\/td><td>prawie normalny dzie\u0144<\/td><td>alarm<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n  <\/div>\n  <p>Rozrzut ma minimum, bo konto wydaj\u0105ce r\u00f3wno 20, 20, 20 mia\u0142oby rozrzut zero i&nbsp;ka\u017cdy cent wygl\u0105da\u0142by jak katastrofa. A&nbsp;dzie\u0144 bez wiersza w&nbsp;eksporcie liczy si\u0119 jako koszt zero, \u017ceby tydzie\u0144 ciszy nie znikn\u0105\u0142 z&nbsp;historii. Suwaki pod wykresem pokazuj\u0105 obie linie na tej samej serii.<\/p>\n  <figure data-reveal=\"\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">\u015arednia kontra mediana po incydencie<\/span>\n      <div class=\"fotc-controls\">\n        <label class=\"fotc-range\">skala incydentu <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\">okno <input type=\"range\" min=\"5\" max=\"14\" step=\"1\" value=\"7\" data-win=\"\"><output data-win-out=\"\">7 dni<\/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\">\u015arednia krocz\u0105ca kontra mediana po trzydniowym incydencie<\/title>\n      <desc id=\"blD\">Koszt dzienny oko\u0142o 20 z trzema dniami incydentu. \u015arednia z okna skacze i przez d\u0142ugo\u015b\u0107 okna udaje now\u0105 norm\u0119; mediana zostaje przy 20.<\/desc>\n    <\/svg>\n    <p class=\"fotc-zout\" data-z-out=\"\"><\/p>\n    <figcaption>Ta sama seria koszt\u00f3w, dwa baseline'y. \u015arednia po incydencie udaje now\u0105 norm\u0119 przez ca\u0142\u0105 d\u0142ugo\u015b\u0107 okna, wi\u0119c kolejne dni incydentu wygl\u0105daj\u0105 na spokojne. Mediana zostaje przy dwudziestu. Ilustracja mechanizmu, o\u015b pierwiastkowa.<\/figcaption>\n  <\/figure>\n  <p>Dwie pu\u0142apki obok. Dzie\u0144 bie\u017c\u0105cy: o&nbsp;sz\u00f3stej rano konto ma za sob\u0105 sze\u015b\u0107 godzin, wi\u0119c por\u00f3wnane z&nbsp;pe\u0142nymi dobami zawsze wygl\u0105da na spadek. Por\u00f3wnujemy wi\u0119c dzisiejsze sze\u015b\u0107 godzin z&nbsp;tymi samymi sze\u015bcioma godzinami poprzednich dni. Detekcja godzinowa: je\u015bli badana godzina siedzi w&nbsp;tej samej pr\u00f3bce, z&nbsp;kt\u00f3rej liczysz norm\u0119, to sama t\u0119 norm\u0119 podnosi i&nbsp;nigdy nie odstanie od niej na tyle, \u017ceby przebi\u0107 pr\u00f3g. Godzina za p\u00f3\u0142 miliona wygl\u0105da wtedy na lekko podwy\u017cszon\u0105. Badan\u0105 godzin\u0119 trzeba z&nbsp;pr\u00f3bki wy\u0142\u0105czy\u0107.<\/p>\n\n  <h2>System musi wiedzie\u0107, czego nie widzi<\/h2>\n  <p>Najgro\u017aniejszy stan detektora anomalii to cisza, kt\u00f3ra wygl\u0105da jak spok\u00f3j.<\/p>\n  <p>25 sierpnia eksport billingowy przesta\u0142 pisa\u0107. Nie jedno \u017ar\u00f3d\u0142o, wszystkie, jedno po drugim w&nbsp;odst\u0119pie kilkudziesi\u0119ciu minut. Detektor chodzi\u0142 dalej co p\u00f3\u0142 godziny, skanowa\u0142 tabele, kt\u00f3re od rana si\u0119 nie zmienia\u0142y, i&nbsp;za ka\u017cdym razem raportowa\u0142, \u017ce nie ma anomalii. Przez sze\u015b\u0107 godzin nikt nie patrzy\u0142 na flot\u0119, a&nbsp;jedynym \u015bladem tego stanu by\u0142 pasek na dashboardzie, na kt\u00f3ry kto\u015b musia\u0142 sam spojrze\u0107.<\/p>\n  <p>Naprawili\u015bmy to jeszcze tego samego dnia i&nbsp;od razu przesadzili\u015bmy: martwe \u017ar\u00f3d\u0142o zacz\u0119\u0142o budzi\u0107 dy\u017curnego. Tej nocy, tu\u017c po p\u00f3\u0142nocy, on-call dosta\u0142 telefon o&nbsp;zamro\u017conej tabeli eksportu, z&nbsp;kt\u00f3r\u0105 i&nbsp;tak nie m\u00f3g\u0142 nic zrobi\u0107 do rana. Nast\u0119pnego dnia wycofali\u015bmy t\u0119 \u015bcie\u017ck\u0119. Pager jest dzi\u015b zarezerwowany dla kosztu, a&nbsp;awaria \u017ar\u00f3d\u0142a ma w\u0142asny kana\u0142 i&nbsp;pr\u00f3g liczony w&nbsp;godzinach milczenia, bo \u017ar\u00f3d\u0142o migaj\u0105ce przez kwadrans nie jest incydentem, a&nbsp;takie, kt\u00f3re milczy p\u00f3\u0142 doby, jest.<\/p>\n  <p>Cztery stany, kt\u00f3re w&nbsp;naiwnym kodzie wygl\u0105daj\u0105 identycznie jak \u201ebrak anomalii\":<\/p>\n  <div class=\"fotc-cards\">\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag\">\u017ar\u00f3d\u0142o stan\u0119\u0142o<\/span><h3>Eksport nie pisze od godzin<\/h3><p>Detektor skanuje zamro\u017cone tabele i&nbsp;raportuje sukces. Potrzebna jest osobna metryka: jak dawno \u017ar\u00f3d\u0142o cokolwiek zapisa\u0142o, z&nbsp;progiem liczonym w&nbsp;godzinach milczenia.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag\">b\u0142\u0105d zamiast wyniku<\/span><h3>Zapytanie pad\u0142o<\/h3><p>Pusta lista, bo zapytanie pad\u0142o, i&nbsp;pusta lista, bo nic nie znaleziono, to dwa r\u00f3\u017cne fakty i&nbsp;kod musi je rozr\u00f3\u017cnia\u0107. Przebieg, kt\u00f3ry nie widzia\u0142 floty, ko\u0144czy si\u0119 b\u0142\u0119dem zamiast raportem.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag\">nie da si\u0119 orzec<\/span><h3>Konto bez pe\u0142nego miesi\u0105ca historii<\/h3><p>Prognoza nie powstaje i&nbsp;s\u0142usznie, bo nie ma z&nbsp;czego. Ale konto jest liczone i&nbsp;widoczne jako \u201enieocenione\", z&nbsp;dat\u0105, od kt\u00f3rej zacznie by\u0107 oceniane.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag\">pusty snapshot<\/span><h3>Zero kont zamiast setek<\/h3><p>Pusty wynik oznacza awari\u0119 przebiegu. Pusta flota zdarza si\u0119 tylko przy pierwszym wdro\u017ceniu i&nbsp;wtedy trzeba to zadeklarowa\u0107 wprost.<\/p><\/div>\n  <\/div>\n  <div class=\"fotc-callout\" data-reveal=\"\"><p><strong>Brak danych to nie zero.<\/strong> Ka\u017cda \u015bcie\u017cka, kt\u00f3ra nie potrafi udowodni\u0107, \u017ce patrzy\u0142a na dane, ko\u0144czy przebieg b\u0142\u0119dem widocznym w&nbsp;monitoringu. Alternatyw\u0105 jest zielony dashboard nad flot\u0105, kt\u00f3rej nikt nie ogl\u0105da.<\/p><\/div>\n\n  <h2>Jak sprawdzi\u0107 to u&nbsp;siebie<\/h2>\n  <p>Trzy komendy odpowiadaj\u0105 na pytanie z&nbsp;tego tekstu: czy pod Twoim kontem rozliczeniowym mo\u017ce pojawi\u0107 si\u0119 projekt, o&nbsp;kt\u00f3rym nikt nie wie. Pierwsza listuje projekty podpi\u0119te do konta z&nbsp;dat\u0105 utworzenia, najnowsze na g\u00f3rze. Druga pokazuje, kto w&nbsp;organizacji mo\u017ce tworzy\u0107 projekty. Trzecia, kto mo\u017ce podpina\u0107 je pod billing.<\/p>\n  <figure data-reveal=\"\" data-trigger=\"terminal\">\n    <div class=\"fotc-fig-head\"><span class=\"fotc-fig-title\">Trzy pytania do konta rozliczeniowego<\/span><\/div>\n    <div class=\"fotc-term\" role=\"img\" aria-label=\"Terminal z trzema komendami gcloud: lista projekt\u00f3w podpi\u0119tych do konta z dat\u0105 utworzenia, lista os\u00f3b z rol\u0105 Project Creator w organizacji, lista os\u00f3b z rol\u0105 Billing Account User. Pod\u015bwietlone: projekt utworzony wczoraj oraz ca\u0142a domena z prawem tworzenia projekt\u00f3w.\">\n      <div class=\"fotc-term-bar\"><i><\/i><i><\/i><i><\/i><span>konto rozliczeniowe \u00b7 przyk\u0142adowe dane<\/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 utworzony wczoraj, zero historii koszt\u00f3w<\/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 ca\u0142a domena mo\u017ce tworzy\u0107 projekty (domy\u015blne)<\/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 klucz sprzed lat, nikt nie pami\u0119ta po co<\/b><\/span><span class=\"tl hl-amber\" data-t=\"out\">serviceAccount:ci-legacy@shop-prod.iam.gserviceaccount.com<\/span><\/pre>\n    <\/div>\n    <figcaption>Dane w&nbsp;terminalu s\u0105 przyk\u0142adowe. Projekt z&nbsp;wczorajsz\u0105 dat\u0105 i&nbsp;bez historii koszt\u00f3w wymaga sprawdzenia \u2013 czy ma w\u0142a\u015bciciela i&nbsp;w\u0142asne limity. Wpis <code>domain:<\/code> przy Project Creator oznacza domy\u015bln\u0105 konfiguracj\u0119 Google Cloud: ka\u017cdy w&nbsp;firmie mo\u017ce za\u0142o\u017cy\u0107 projekt.<\/figcaption>\n  <\/figure>\n  <p>Te same trzy komendy do skopiowania w&nbsp;ca\u0142o\u015bci. Uzupe\u0142niasz dwie zmienne na g\u00f3rze i&nbsp;wklejasz reszt\u0119 bez zmian. Identyfikator konta znajdziesz w&nbsp;<code>gcloud billing accounts list<\/code>, numer organizacji w&nbsp;<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=\"\">Kopiuj<\/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\n\necho \"== 1. Projekty podpi\u0119te do konta $BILLING_ACCOUNT_ID, najnowsze na g\u00f3rze ==\"\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\n\necho; echo \"== 2. Kto mo\u017ce tworzy\u0107 projekty w organizacji $ORG_ID ==\"\ngcloud organizations get-iam-policy \"$ORG_ID\" --flatten=\"bindings[].members\" \\\n  --filter=\"bindings.role:roles\/resourcemanager.projectCreator\" --format=\"value(bindings.members)\"\n\necho; echo \"== 3. Kto mo\u017ce podpina\u0107 projekty pod konto $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\">krok 1<\/span><h3>Przejrzyj list\u0119 projekt\u00f3w<\/h3><p>Ka\u017cdy projekt, kt\u00f3rego nie rozpoznajesz, dostaje w\u0142a\u015bciciela albo znika. Powtarzaj co tydzie\u0144, najlepiej z&nbsp;harmonogramu i&nbsp;mailem.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag fotc-tag--blue\">krok 2<\/span><h3>Zaw\u0119\u017a Project Creator i&nbsp;Billing Account User<\/h3><p>Odbierz domenie domy\u015bln\u0105 rol\u0119, zostaw grup\u0119 administrator\u00f3w. Lista z&nbsp;prawem do podpinania billingu ma mie\u015bci\u0107 si\u0119 na jednej d\u0142oni.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag fotc-tag--blue\">krok 3<\/span><h3>Limit na ka\u017cdej parze projekt&nbsp;\u00d7&nbsp;us\u0142uga AI<\/h3><p>Tak\u017ce tam, gdzie AI nie jest u\u017cywane. Kwota symboliczna, zaokr\u0105glona w&nbsp;g\u00f3r\u0119 od historii tam, gdzie ruch jest realny.<\/p><\/div>\n    <div class=\"fotc-card\" data-reveal=\"\"><span class=\"fotc-tag fotc-tag--blue\">krok 4<\/span><h3>Bud\u017cet na ca\u0142e konto<\/h3><p>Z&nbsp;progiem dobranym do realnych wydatk\u00f3w, nie do okr\u0105g\u0142ej liczby. Obejmie tak\u017ce wydatki nowych projekt\u00f3w podpi\u0119tych do tego konta.<\/p><\/div>\n  <\/div>\n  <p>Te cztery kroki zajmuj\u0105 popo\u0142udnie i&nbsp;nie wymagaj\u0105 \u017cadnego narz\u0119dzia poza konsol\u0105. Zamykaj\u0105 scenariusz z&nbsp;tego tekstu w&nbsp;ma\u0142ym \u015brodowisku i&nbsp;mocno go utrudniaj\u0105 w&nbsp;du\u017cym.<\/p>\n\n  <h2>Podsumowanie<\/h2>\n  <p>Spend Caps w&nbsp;Google Cloud automatycznie egzekwuj\u0105 ustalon\u0105 kwot\u0119 dla obj\u0119tych nimi us\u0142ug. Zak\u0142adaj je wsz\u0119dzie, gdzie si\u0119 da, ze szczeg\u00f3lnym uwzgl\u0119dnieniem projekt\u00f3w, kt\u00f3re AI nie u\u017cywaj\u0105. Cloud Anomaly Detector dok\u0142ada drug\u0105 warstw\u0119: por\u00f3wnuje wydatki z&nbsp;histori\u0105 konta, pokazuje, co si\u0119 zmieni\u0142o, i&nbsp;budzi kogo\u015b, kto mo\u017ce zareagowa\u0107. Nietypowy wydatek mo\u017ce wymaga\u0107 uwagi tak\u017ce wtedy, gdy mie\u015bci si\u0119 w&nbsp;miesi\u0119cznym limicie.<\/p>\n  <div class=\"fotc-callout\" data-reveal=\"\"><p>Spend Caps pilnuj\u0105 ustalonej kwoty. Cloud Anomaly Detector pomaga rozpozna\u0107 nietypowy koszt i&nbsp;ustali\u0107, co z&nbsp;nim zrobi\u0107.<\/p><\/div>\n  <p>Cztery kroki wy\u017cej wystarcz\u0105, \u017ceby zacz\u0105\u0107. Gdy \u015brodowisko ro\u015bnie, sprawdzaj, czy limity nadal obejmuj\u0105 to, co powinny. Granica jest tam, gdzie zaczyna si\u0119 pytanie \u201eczy to atak, czy nowy feature\" przy kilkudziesi\u0119ciu projektach i&nbsp;kluczach rozdanych latami. W&nbsp;FOTC robimy to jako Cloud Anomaly Detector: monitoring konta rozliczeniowego co p\u00f3\u0142 godziny z&nbsp;alertami do Twojego zespo\u0142u i&nbsp;do naszego, a&nbsp;gdy alert ju\u017c przyszed\u0142 i&nbsp;nie wiesz, co z&nbsp;nim zrobi\u0107, tak\u017ce jako godzinowe wsparcie in\u017cyniera bez d\u0142ugiej umowy. Je\u015bli wolisz zrobi\u0107 to sam, wszystko powy\u017cej jest wystarczaj\u0105ce, \u017ceby zacz\u0105\u0107.<\/p>\n\n  <section class=\"fotc-faq\">\n    <h2>FAQ<\/h2>\n    <h3>Czy limit wydatk\u00f3w uchroni mnie przed LLMjackingiem?<\/h3>\n    <p>Ograniczy rachunek w&nbsp;projektach, kt\u00f3re nim obj\u0105\u0142e\u015b. Nie zatrzyma ataku: egzekucja nie jest natychmiastowa, \u017c\u0105dania w&nbsp;locie zostan\u0105 zafakturowane, a&nbsp;atakuj\u0105cy z&nbsp;prawem do tworzenia projekt\u00f3w za\u0142o\u017cy nowy. Limit, detekcja na poziomie konta i&nbsp;przegl\u0105d r\u00f3l Project Creator oraz Billing Account User zamykaj\u0105 trzy r\u00f3\u017cne luki. \u017baden z&nbsp;nich nie zast\u0105pi pozosta\u0142ych.<\/p>\n    <h3>Po co Cloud Anomaly Detector, je\u015bli mam Spend Caps?<\/h3>\n    <p>Spend Caps wstrzymuj\u0105 u\u017cycie wskazanej us\u0142ugi po przekroczeniu ustalonej kwoty. Detektor szuka odchyle\u0144 od normalnych wydatk\u00f3w konta i&nbsp;pomaga wskaza\u0107 projekt oraz us\u0142ug\u0119 odpowiedzialne za zmian\u0119. Mo\u017ce zg\u0142osi\u0107 anomali\u0119 mieszcz\u0105c\u0105 si\u0119 w&nbsp;limicie, o&nbsp;ile spe\u0142nia ona warunki alertowania. Korzysta z&nbsp;eksportu billingowego, wi\u0119c czas detekcji zale\u017cy od dost\u0119pno\u015bci danych. Nie ma gwarancji, \u017ce alert pojawi si\u0119 przed zadzia\u0142aniem Spend Caps.<\/p>\n    <h3>Jak\u0105 kwot\u0119 ustawi\u0107 w&nbsp;projekcie, kt\u00f3ry nie u\u017cywa AI?<\/h3>\n    <p>Symboliczn\u0105, rz\u0119du stu euro miesi\u0119cznie. Tyle, \u017ceby nikt nie zauwa\u017cy\u0142 jej przy legalnym te\u015bcie, i&nbsp;na tyle ma\u0142o, \u017ceby zatrzyma\u0142a nadu\u017cycie, zanim uro\u015bnie. Gdy projekt zaczyna AI u\u017cywa\u0107 naprawd\u0119, limit podnosisz \u015bwiadomie.<\/p>\n    <h3>Czy limit mo\u017ce zatrzyma\u0107 mi produkcj\u0119?<\/h3>\n    <p>Tak, i&nbsp;to jest jego zadanie. \u0179le dobrana kwota na projekcie z&nbsp;realnym ruchem zatrzyma legaln\u0105 inferencj\u0119, a&nbsp;zdj\u0119cie limitu wymaga cz\u0142owieka i&nbsp;trwa do godziny. Dlatego na projektach produkcyjnych kwota wynika z&nbsp;historii i&nbsp;zaokr\u0105gla si\u0119 w&nbsp;g\u00f3r\u0119, a&nbsp;kto\u015b z&nbsp;dost\u0119pem do bud\u017cetu musi by\u0107 osi\u0105galny tak\u017ce w&nbsp;weekend.<\/p>\n    <h3>Co je\u015bli eksport billingowy do BigQuery przestanie dzia\u0142a\u0107?<\/h3>\n    <p>To jedyny stan, kt\u00f3rego detektor kosztowy sam nie zauwa\u017cy, bo brak danych wygl\u0105da jak brak wydatk\u00f3w. \u015awie\u017co\u015b\u0107 \u017ar\u00f3d\u0142a musi by\u0107 osobn\u0105 metryk\u0105 z&nbsp;osobnym alarmem, a&nbsp;dashboard ma m\u00f3wi\u0107 \u201enie widz\u0119\" innym kolorem ni\u017c \u201enic si\u0119 nie dzieje\".<\/p>\n  <\/section>\n\n  <p class=\"fotc-note\">Opis limit\u00f3w wydatk\u00f3w pochodzi z&nbsp;<a href=\"https:\/\/docs.cloud.google.com\/billing\/docs\/how-to\/budgets-spend-caps\">publicznej dokumentacji Google Cloud<\/a>, sprawdzonej 14 wrze\u015bnia 2026. Dokumentacja nadal oznacza Spend Caps w&nbsp;Cloud Billing jako Preview i&nbsp;zapowiada rozszerzenie listy us\u0142ug. Liczby o&nbsp;pracy detektora s\u0105 rzeczywiste i&nbsp;pochodz\u0105 z&nbsp;jego log\u00f3w. Mapa pokrycia i&nbsp;wykres baseline'u s\u0105 ilustracjami mechanizmu, nie zrzutami z&nbsp;produkcji. Artyku\u0142 opisuje system w&nbsp;wersji 6.x (2026).<\/p>\n\n  <div class=\"fotc-tip\" id=\"fotcTip\" role=\"presentation\"><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Limity wydatk\u00f3w (spend caps) w&nbsp;Google Cloud pozwalaj\u0105 automatycznie wstrzyma\u0107 u\u017cycie obj\u0119tej nimi us\u0142ugi po przekroczeniu ustalonej kwoty. Cloud Anomaly Detector pomaga odpowiedzie\u0107 na inne pytanie \u2013 czy wydatki s\u0105 normalne dla tego konta i&nbsp;co spowodowa\u0142o zmian\u0119. Limit pilnuje kwoty. Detektor m\u00f3wi, gdzie szuka\u0107 przyczyny. Je\u015bli odpowiadasz za konto rozliczeniowe Google Cloud, na kt\u00f3rym ktokolwiek ma&#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":[560],"tags":[],"class_list":["post-176975","post","type-post","status-publish","format-standard","hentry","category-google-cloud-platform-pl"],"_links":{"self":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts\/176975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/comments?post=176975"}],"version-history":[{"count":0,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts\/176975\/revisions"}],"wp:attachment":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/media?parent=176975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/categories?post=176975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/tags?post=176975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}